duidui_fiber/internal/order/service/order_service.go
2026-03-27 10:34:03 +08:00

461 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"fmt"
"strconv"
"time"
camp_dao "dd_fiber_api/internal/camp/dao"
"dd_fiber_api/internal/order"
order_dao "dd_fiber_api/internal/order/dao"
"dd_fiber_api/internal/payment"
"dd_fiber_api/internal/scheduler"
"dd_fiber_api/pkg/snowflake"
)
// OrderService 订单服务
type OrderService struct {
orderDAO *order_dao.OrderDAO
sectionDAO *camp_dao.SectionDAO // 需要查询小节价格
accessDAO *camp_dao.UserSectionAccessDAO // 用户小节访问记录 DAO
userCampDAO *camp_dao.UserCampDAO // 用户打卡营 DAO用于更新当前小节
wechatPayService *payment.WechatPayV3Service // 微信支付服务
schedulerService *scheduler.Service // 调度器服务(用于创建定时任务)
apiBaseURL string // API基础URL用于构建回调URL
}
// NewOrderService 创建订单服务
func NewOrderService(orderDAO *order_dao.OrderDAO, sectionDAO *camp_dao.SectionDAO) *OrderService {
return &OrderService{
orderDAO: orderDAO,
sectionDAO: sectionDAO,
}
}
// SetAccessDAO 设置访问记录 DAO
func (s *OrderService) SetAccessDAO(accessDAO *camp_dao.UserSectionAccessDAO) {
s.accessDAO = accessDAO
}
// SetUserCampDAO 设置用户打卡营 DAO
func (s *OrderService) SetUserCampDAO(userCampDAO *camp_dao.UserCampDAO) {
s.userCampDAO = userCampDAO
}
// SetPaymentService 设置支付服务用于创建订单后生成预支付ID
func (s *OrderService) SetPaymentService(wechatPayService *payment.WechatPayV3Service) {
s.wechatPayService = wechatPayService
}
// SetSchedulerService 设置调度器服务(用于创建定时任务)
func (s *OrderService) SetSchedulerService(schedulerService *scheduler.Service) {
s.schedulerService = schedulerService
}
// SetAPIBaseURL 设置API基础URL用于构建回调URL
func (s *OrderService) SetAPIBaseURL(baseURL string) {
s.apiBaseURL = baseURL
}
// CreateOrder 创建订单(适配新的 orders 表,并集成支付功能)
func (s *OrderService) CreateOrder(ctx context.Context, req *order.CreateOrderRequest) (*order.CreateOrderResponse, error) {
// 查询小节信息,获取价格
section, err := s.sectionDAO.GetByID(req.SectionID)
if err != nil {
return &order.CreateOrderResponse{
Success: false,
Message: fmt.Sprintf("查询小节失败: %v", err),
}, nil
}
// 验证小节是否属于指定的打卡营
if section.CampID != req.CampID {
return &order.CreateOrderResponse{
Success: false,
Message: "小节不属于指定的打卡营",
}, nil
}
// 生成订单ID
orderID := snowflake.GetInstance().NextIDString()
// 确定订单金额
originalAmount := section.PriceFen
if originalAmount < 0 {
originalAmount = 0
}
// 计算优惠金额暂时为0后续可以集成优惠券逻辑
discountAmount := int32(0)
// 计算实际支付金额
actualAmount := originalAmount - discountAmount
if actualAmount < 0 {
actualAmount = 0
}
// 确定订单状态如果实际支付金额为0直接设为已支付否则设为待支付
orderStatus := order.OrderStatusPending
var paymentTime *time.Time
if actualAmount == 0 {
// 0元订单直接完成设置支付时间为当前时间
orderStatus = order.OrderStatusPaid
now := time.Now()
paymentTime = &now
}
// 确定支付方式如果未指定0元订单不需要支付方式其他订单默认为微信支付
paymentMethod := req.PaymentMethod
if paymentMethod == order.PaymentMethodUnknown {
if actualAmount > 0 {
paymentMethod = order.PaymentMethodWechat
}
// 0元订单保持 PaymentMethodUnknown
}
// 创建订单(使用新的 orders 表)
err = s.orderDAO.CreateOrder(
orderID,
req.UserID,
order.OrderTypeCampSection,
originalAmount,
discountAmount,
actualAmount,
req.CouponID,
orderStatus,
paymentMethod,
paymentTime, // 0元订单的支付时间
)
if err != nil {
return &order.CreateOrderResponse{
Success: false,
Message: fmt.Sprintf("创建订单失败: %v", err),
}, nil
}
// 创建订单业务数据(关联订单和小节)
// 注意:如果 order_business_data 表不存在,这个调用会失败但不影响订单创建
if req.CampID != "" && req.SectionID != "" {
_ = s.orderDAO.CreateOrderBusinessData(orderID, req.CampID, req.SectionID)
}
// 如果是0元订单已支付创建访问记录
if actualAmount == 0 && orderStatus == order.OrderStatusPaid && s.accessDAO != nil {
accessID := snowflake.GetInstance().NextIDString()
paidPriceFen := int32(0)
if section.PriceFen > 0 {
paidPriceFen = section.PriceFen
}
// 创建访问记录0元订单
if err := s.accessDAO.Create(
accessID,
req.UserID,
req.CampID,
req.SectionID,
paidPriceFen,
camp_dao.AccessSourcePurchase,
); err != nil {
// 记录错误但不影响订单创建
fmt.Printf("创建访问记录失败0元订单: %v\n", err)
} else {
// 更新当前小节
if s.userCampDAO != nil && req.CampID != "" {
if err := s.userCampDAO.UpdateCurrentSection(req.UserID, req.CampID, req.SectionID); err != nil {
fmt.Printf("更新当前小节失败0元订单: %v\n", err)
} else {
fmt.Printf("✅ 当前小节更新成功0元订单: user_id=%s, camp_id=%s, section_id=%s\n", req.UserID, req.CampID, req.SectionID)
}
}
}
}
// 构建响应
resp := &order.CreateOrderResponse{
Success: true,
Message: "订单创建成功",
OrderID: orderID,
OrderStatus: orderStatus,
ActualAmount: actualAmount,
}
// 如果订单状态是待支付创建15分钟后自动关闭订单的定时任务
if orderStatus == order.OrderStatusPending && s.schedulerService != nil && s.apiBaseURL != "" {
// 构建回调URL
callbackURL := fmt.Sprintf("%s/api/v1/order/auto-cancel?order_id=%s", s.apiBaseURL, orderID)
// 创建定时任务15分钟后执行
taskReq := &scheduler.AddTaskRequest{
BusinessKey: fmt.Sprintf("order_auto_cancel_%s", orderID),
TaskType: scheduler.TaskTypeOnce,
DelayMs: 15 * 60 * 1000, // 15分钟 = 15 * 60 * 1000 毫秒
CallbackURL: callbackURL,
Metadata: map[string]string{
"order_id": orderID,
"type": "order_auto_cancel",
},
}
taskResp, err := s.schedulerService.AddTask(taskReq)
if err == nil && taskResp != nil && taskResp.Success {
fmt.Printf("✅ 创建订单自动关闭任务成功: order_id=%s, task_id=%s\n", orderID, taskResp.TaskID)
} else {
fmt.Printf("⚠️ 创建订单自动关闭任务失败: order_id=%s, error=%v\n", orderID, err)
}
}
// 如果订单需要支付且是微信支付生成预支付ID
if actualAmount > 0 && orderStatus == order.OrderStatusPending && paymentMethod == order.PaymentMethodWechat {
if s.wechatPayService != nil && req.OpenID != "" {
// 生成商品描述
description := fmt.Sprintf("打卡营小节-%s", section.Title)
if len(description) > 127 {
description = description[:127] // 微信支付描述最长127字符
}
// 调用微信支付服务生成预支付ID
payReq := &payment.CreateWechatPayV3Request{
OrderID: orderID,
Description: description,
OpenID: req.OpenID,
Amount: actualAmount,
}
payResp, err := s.wechatPayService.CreateWechatPayV3(ctx, payReq)
if err == nil && payResp != nil && payResp.Success {
// 将支付信息添加到响应中
resp.PrepayID = payResp.PrepayID
resp.AppID = payResp.AppID
resp.TimeStamp = payResp.TimeStamp
resp.NonceStr = payResp.NonceStr
resp.Package = payResp.Package
resp.SignType = payResp.SignType
resp.PaySign = payResp.PaySign
} else {
// 支付创建失败,但不影响订单创建
// 可以记录日志,但不返回错误
}
}
}
return resp, nil
}
// GetOrder 获取订单详情
func (s *OrderService) GetOrder(orderID, orderNo string) (*order.GetOrderResponse, error) {
if orderID == "" && orderNo == "" {
return &order.GetOrderResponse{
Success: false,
Message: "参数缺失order_id 和 order_no 至少需要提供一个",
}, nil
}
var orderObj *order.Order
var err error
if orderID != "" {
orderObj, err = s.orderDAO.GetOrderByID(orderID)
} else {
orderObj, err = s.orderDAO.GetOrderByOrderNo(orderNo)
}
if err != nil {
return &order.GetOrderResponse{
Success: false,
Message: fmt.Sprintf("查询订单失败: %v", err),
}, nil
}
if orderObj == nil {
return &order.GetOrderResponse{
Success: false,
Message: "订单不存在",
}, nil
}
return &order.GetOrderResponse{
Success: true,
Message: "查询成功",
Order: orderObj,
}, nil
}
// ListOrders 查询订单列表
func (s *OrderService) ListOrders(req *order.ListOrdersRequest) (*order.ListOrdersResponse, error) {
// 设置默认分页参数
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100 // 限制最大每页数量
}
orders, total, err := s.orderDAO.ListOrders(
req.UserID,
req.CampID,
req.SectionID,
req.OrderStatus,
req.PaymentMethod,
page,
pageSize,
)
if err != nil {
return &order.ListOrdersResponse{
Success: false,
Message: fmt.Sprintf("查询订单列表失败: %v", err),
}, nil
}
return &order.ListOrdersResponse{
Success: true,
Message: "查询成功",
Orders: orders,
Total: total,
}, nil
}
// UpdateOrderStatus 更新订单状态(用于支付回调)
func (s *OrderService) UpdateOrderStatus(req *order.UpdateOrderStatusRequest) (*order.UpdateOrderStatusResponse, error) {
if req.OrderID == "" && req.OrderNo == "" {
return &order.UpdateOrderStatusResponse{
Success: false,
Message: "参数缺失order_id 和 order_no 至少需要提供一个",
}, nil
}
// 解析支付时间
var paymentTime *time.Time
if req.PaymentTime != "" {
// 尝试解析 Unix 时间戳
if sec, err := strconv.ParseInt(req.PaymentTime, 10, 64); err == nil {
t := time.Unix(sec, 0)
paymentTime = &t
} else {
// 尝试解析 RFC3339 格式
if t, err := time.Parse(time.RFC3339, req.PaymentTime); err == nil {
paymentTime = &t
}
}
}
// 更新订单状态
err := s.orderDAO.UpdateOrderStatus(
req.OrderID,
req.OrderNo,
req.OrderStatus,
req.PaymentMethod,
req.ThirdPartyOrderNo,
paymentTime,
)
if err != nil {
return &order.UpdateOrderStatusResponse{
Success: false,
Message: fmt.Sprintf("更新订单状态失败: %v", err),
}, nil
}
// 查询更新后的订单
var orderObj *order.Order
if req.OrderID != "" {
orderObj, err = s.orderDAO.GetOrderByID(req.OrderID)
} else {
orderObj, err = s.orderDAO.GetOrderByOrderNo(req.OrderNo)
}
if err != nil {
return &order.UpdateOrderStatusResponse{
Success: false,
Message: fmt.Sprintf("查询订单失败: %v", err),
}, nil
}
// 如果订单状态更新为已支付,取消自动关闭订单的定时任务
if req.OrderStatus == order.OrderStatusPaid && s.schedulerService != nil && orderObj != nil {
// 通过 business_key 查找任务(需要调度器服务支持通过 business_key 查询)
// 目前调度器服务只支持通过 task_id 删除,所以这里先记录日志
// 后续可以优化调度器服务,支持通过 business_key 删除任务
businessKey := fmt.Sprintf("order_auto_cancel_%s", orderObj.OrderID)
fmt.Printf("订单已支付,需要取消自动关闭任务: order_id=%s, business_key=%s\n", orderObj.OrderID, businessKey)
// TODO: 实现通过 business_key 删除任务的逻辑
}
// 如果订单状态更新为已支付且访问记录DAO已设置创建访问记录
if req.OrderStatus == order.OrderStatusPaid && s.accessDAO != nil && orderObj != nil && orderObj.SectionID != "" {
// 检查是否已存在访问记录(幂等性检查)
hasAccess, err := s.accessDAO.GetByUserAndSection(orderObj.UserID, orderObj.SectionID)
if err == nil && !hasAccess {
// 生成访问记录ID使用雪花算法
accessID := snowflake.GetInstance().NextIDString()
// 创建访问记录
paidPriceFen := int32(orderObj.ActualAmount)
if paidPriceFen < 0 {
paidPriceFen = 0
}
err = s.accessDAO.Create(
accessID,
orderObj.UserID,
orderObj.CampID,
orderObj.SectionID,
paidPriceFen,
camp_dao.AccessSourcePurchase,
)
if err != nil {
// 记录错误但不影响订单状态更新
fmt.Printf("创建访问记录失败: %v\n", err)
} else {
fmt.Printf("✅ 访问记录创建成功: user_id=%s, section_id=%s\n", orderObj.UserID, orderObj.SectionID)
// 更新当前小节
if s.userCampDAO != nil && orderObj.CampID != "" {
if err := s.userCampDAO.UpdateCurrentSection(orderObj.UserID, orderObj.CampID, orderObj.SectionID); err != nil {
fmt.Printf("更新当前小节失败: %v\n", err)
} else {
fmt.Printf("✅ 当前小节更新成功: user_id=%s, camp_id=%s, section_id=%s\n", orderObj.UserID, orderObj.CampID, orderObj.SectionID)
}
}
}
}
}
return &order.UpdateOrderStatusResponse{
Success: true,
Message: "更新成功",
Order: orderObj,
}, nil
}
// CancelOrder 取消订单
func (s *OrderService) CancelOrder(req *order.CancelOrderRequest) (*order.CancelOrderResponse, error) {
if req.OrderID == "" && req.OrderNo == "" {
return &order.CancelOrderResponse{
Success: false,
Message: "参数缺失order_id 和 order_no 至少需要提供一个",
}, nil
}
// 使用 UpdateOrderStatus 来取消订单
err := s.orderDAO.UpdateOrderStatus(
req.OrderID,
req.OrderNo,
order.OrderStatusCancelled,
order.PaymentMethodUnknown,
"",
nil,
)
if err != nil {
return &order.CancelOrderResponse{
Success: false,
Message: fmt.Sprintf("取消订单失败: %v", err),
}, nil
}
return &order.CancelOrderResponse{
Success: true,
Message: "取消订单成功",
}, nil
}