461 lines
14 KiB
Go
461 lines
14 KiB
Go
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
|
||
}
|