package order import ( "backend/internal/dto" "backend/internal/model" "backend/utils" "errors" "fmt" "time" "gorm.io/gorm" ) // OrderService 订单业务逻辑层 // Logan.Li type OrderService struct { db *gorm.DB } // NewOrderService 创建 service 实例 // Logan.Li func NewOrderService(db *gorm.DB) *OrderService { return &OrderService{db: db} } // List 分页查询订单 // Logan.Li func (s *OrderService) List(page, pageSize int, status string, productID uint) ([]dto.OrderResponse, int64, error) { var orders []model.Order var total int64 query := s.db.Model(&model.Order{}).Preload("Customer").Preload("Product").Preload("Plan") if status != "" { query = query.Where("status = ?", status) } if productID > 0 { query = query.Where("product_id = ?", productID) } query.Count(&total) if err := query.Order("created_at DESC"). Offset((page - 1) * pageSize).Limit(pageSize). Find(&orders).Error; err != nil { return nil, 0, err } results := make([]dto.OrderResponse, 0, len(orders)) for _, o := range orders { results = append(results, toOrderResponse(o)) } return results, total, nil } // GetByID 获取订单详情 // Logan.Li func (s *OrderService) GetByID(id uint) (*dto.OrderResponse, error) { var order model.Order if err := s.db.Preload("Customer").Preload("Product").Preload("Plan").First(&order, id).Error; err != nil { return nil, errors.New("订单不存在") } resp := toOrderResponse(order) return &resp, nil } // CreateOrder 客户端下单(含防重复购买检查) // Logan.Li func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAgent string) (*dto.CreateOrderResponse, error) { // 1. 查找或创建客户 // Logan.Li var customer model.Customer if err := s.db.Where("email = ?", req.CustomerEmail).First(&customer).Error; err != nil { name := req.CustomerName if name == "" { name = req.CustomerEmail } customer = model.Customer{Name: name, Email: req.CustomerEmail, Status: 1} if err := s.db.Create(&customer).Error; err != nil { return nil, errors.New("创建客户失败") } } // 2. 校验产品与计划 // Logan.Li var product model.Product if err := s.db.First(&product, req.ProductID).Error; err != nil { return nil, errors.New("产品不存在") } var plan model.Plan if err := s.db.First(&plan, req.PlanID).Error; err != nil { return nil, errors.New("定价计划不存在") } // 3. 防重复购买检查 // Logan.Li checkResult := s.CheckDuplicatePurchase(customer.ID, req.ProductID, plan.PlanType) if !checkResult.AllowPurchase { return nil, fmt.Errorf("重复购买拦截: %s", checkResult.Reason) } // 4. 创建订单 // Logan.Li order := model.Order{ OrderNo: utils.GenerateOrderNo(), CustomerID: customer.ID, ProductID: req.ProductID, PlanID: req.PlanID, Amount: plan.Price, Currency: plan.Currency, Status: "pending", ClientIP: clientIP, UserAgent: userAgent, } if err := s.db.Create(&order).Error; err != nil { return nil, errors.New("创建订单失败") } // 5. 选取可用支付渠道 // Logan.Li var gateway model.PaymentGatewayConfig if err := s.db.Where("enabled = ?", true).Order("priority ASC").First(&gateway).Error; err != nil { return nil, errors.New("暂无可用的支付渠道") } // 6. 生成收银台地址(占位) // Logan.Li checkoutURL := fmt.Sprintf("https://checkout.%s.com/?order_no=%s", gateway.Gateway, order.OrderNo) return &dto.CreateOrderResponse{ OrderNo: order.OrderNo, CheckoutURL: checkoutURL, Amount: plan.Price, Currency: plan.Currency, Status: "pending", Gateway: gateway.Gateway, ExpiresAt: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"), }, nil } // PaymentWebhook 支付回调(幂等 + 事务) // Logan.Li func (s *OrderService) PaymentWebhook(req dto.PaymentWebhookRequest, clientIP string) error { // 幂等检查 // Logan.Li var order model.Order if err := s.db.Where("order_no = ?", req.OrderNo).First(&order).Error; err != nil { return errors.New("订单不存在") } if order.Status == "paid" || order.Status == "refunded" { return nil // 已处理,幂等返回 } // 事务处理 // Logan.Li return s.db.Transaction(func(tx *gorm.DB) error { now := time.Now() switch req.Status { case "paid", "completed": // 更新订单 // Logan.Li order.Status = "paid" order.GatewayOrderNo = req.GatewayOrderNo order.PaymentGateway = req.Gateway order.PaymentMethod = req.Gateway order.PaidAt = &now if err := tx.Save(&order).Error; err != nil { return err } // 发放 License // Logan.Li var plan model.Plan tx.First(&plan, order.PlanID) var validUntil *time.Time var endDate time.Time if plan.Duration == 0 { endDate = model.PermanentEndDate } else { endDate = now.AddDate(0, 0, plan.Duration) validUntil = &endDate } lic := model.License{ LicenseKey: utils.GenerateLicenseKey(), CustomerID: order.CustomerID, ProductID: order.ProductID, Status: model.LicenseStatusActive, StartDate: now, EndDate: endDate, Duration: plan.Duration, MaxActivations: plan.MaxDevices, } if err := tx.Create(&lic).Error; err != nil { return err } order.LicenseID = lic.ID tx.Save(&order) // 更新权益记录(防重复购买核心) // Logan.Li var ent model.CustomerEntitlement entResult := tx.Where("customer_id = ? AND product_id = ?", order.CustomerID, order.ProductID).First(&ent) renewable := plan.PlanType != "perpetual" && plan.PlanType != "lifetime" if entResult.Error != nil { ent = model.CustomerEntitlement{ CustomerID: order.CustomerID, ProductID: order.ProductID, PlanID: order.PlanID, LicenseID: lic.ID, LicenseKey: lic.LicenseKey, PlanType: plan.PlanType, Status: "active", ValidFrom: now, ValidUntil: validUntil, Renewable: renewable, } tx.Create(&ent) } else { ent.PlanID = order.PlanID ent.LicenseID = lic.ID ent.LicenseKey = lic.LicenseKey ent.PlanType = plan.PlanType ent.Status = "active" ent.ValidFrom = now ent.ValidUntil = validUntil ent.Renewable = renewable tx.Save(&ent) } utils.LogSystemAction(tx, "payment_completed", "order", order.ID, order.OrderNo, fmt.Sprintf("订单支付成功,已发放 License: %s", lic.LicenseKey), clientIP) case "failed", "cancelled": order.Status = req.Status if err := tx.Save(&order).Error; err != nil { return err } case "refunded": order.Status = "refunded" order.RefundAt = &now if err := tx.Save(&order).Error; err != nil { return err } // 吊销关联 License // Logan.Li if order.LicenseID > 0 { tx.Model(&model.License{}).Where("id = ?", order.LicenseID). Updates(map[string]interface{}{"status": model.LicenseStatusRevoked, "revoke_reason": "订单退款"}) tx.Model(&model.CustomerEntitlement{}).Where("license_id = ?", order.LicenseID). Update("status", "revoked") } } return nil }) } // ManualGrant 手动发放 License(管理后台) // Logan.Li func (s *OrderService) ManualGrant(req dto.ManualGrantRequest, operatorID uint, operatorName, clientIP string) (map[string]interface{}, error) { // 查找或创建客户 // Logan.Li var customer model.Customer if err := s.db.Where("email = ?", req.CustomerEmail).First(&customer).Error; err != nil { name := req.CustomerName if name == "" { name = req.CustomerEmail } customer = model.Customer{Name: name, Email: req.CustomerEmail, Status: 1} if err := s.db.Create(&customer).Error; err != nil { return nil, errors.New("创建客户失败") } } var plan model.Plan if err := s.db.First(&plan, req.PlanID).Error; err != nil { return nil, errors.New("定价计划不存在") } now := time.Now() var endDate time.Time var validUntil *time.Time if plan.Duration == 0 { endDate = model.PermanentEndDate } else { endDate = now.AddDate(0, 0, plan.Duration) validUntil = &endDate } lic := model.License{ LicenseKey: utils.GenerateLicenseKey(), CustomerID: customer.ID, ProductID: req.ProductID, Status: model.LicenseStatusActive, StartDate: now, EndDate: endDate, Duration: plan.Duration, MaxActivations: plan.MaxDevices, Notes: req.Notes, } if err := s.db.Create(&lic).Error; err != nil { return nil, errors.New("创建 License 失败") } // 创建/更新权益记录 // Logan.Li renewable := plan.PlanType != "perpetual" && plan.PlanType != "lifetime" var ent model.CustomerEntitlement if err := s.db.Where("customer_id = ? AND product_id = ?", customer.ID, req.ProductID).First(&ent).Error; err != nil { ent = model.CustomerEntitlement{ CustomerID: customer.ID, ProductID: req.ProductID, PlanID: req.PlanID, LicenseID: lic.ID, LicenseKey: lic.LicenseKey, PlanType: plan.PlanType, Status: "active", ValidFrom: now, ValidUntil: validUntil, Renewable: renewable, } s.db.Create(&ent) } else { ent.PlanID = req.PlanID ent.LicenseID = lic.ID ent.LicenseKey = lic.LicenseKey ent.PlanType = plan.PlanType ent.Status = "active" ent.ValidFrom = now ent.ValidUntil = validUntil ent.Renewable = renewable s.db.Save(&ent) } utils.LogSystemAction(s.db, "manual_grant", "license", lic.ID, lic.LicenseKey, fmt.Sprintf("手动发放 License: %s 给客户 %s", lic.LicenseKey, req.CustomerEmail), clientIP) return map[string]interface{}{ "license_key": lic.LicenseKey, "license_id": lic.ID, "customer_id": customer.ID, }, nil } // CheckDuplicatePurchase 防重复购买检测 // Logan.Li func (s *OrderService) CheckDuplicatePurchase(customerID, productID uint, newPlanType string) dto.DuplicatePurchaseCheckResponse { var entitlements []model.CustomerEntitlement s.db.Where("customer_id = ? AND product_id = ?", customerID, productID).Find(&entitlements) if len(entitlements) == 0 { return dto.DuplicatePurchaseCheckResponse{AllowPurchase: true} } // 已有有效的永久/买断授权:禁止重复购买,但可升级 // Logan.Li for _, ent := range entitlements { if ent.Status != "active" { continue } switch ent.PlanType { case "perpetual", "lifetime": return dto.DuplicatePurchaseCheckResponse{ AllowPurchase: false, Reason: "您已拥有该产品的永久授权,无需重复购买", ExistingEntitlementID: ent.ID, ExistingLicenseKey: ent.LicenseKey, ExistingPlanType: ent.PlanType, CanUpgrade: true, } case "monthly", "yearly": // 订阅可续费 // Logan.Li if ent.Renewable && (newPlanType == "monthly" || newPlanType == "yearly") { return dto.DuplicatePurchaseCheckResponse{ AllowPurchase: true, Reason: "您有有效的订阅,将为您续费", ExistingEntitlementID: ent.ID, ExistingLicenseKey: ent.LicenseKey, ExistingPlanType: ent.PlanType, CanUpgrade: true, } } } } // 已过期但可续费 // Logan.Li for _, ent := range entitlements { if ent.Status == "expired" && ent.Renewable { return dto.DuplicatePurchaseCheckResponse{ AllowPurchase: true, Reason: "您的订阅已过期,可以续费恢复", ExistingEntitlementID: ent.ID, ExistingLicenseKey: ent.LicenseKey, ExistingPlanType: ent.PlanType, CanUpgrade: true, } } } return dto.DuplicatePurchaseCheckResponse{AllowPurchase: true} } // CheckPurchaseStatus 查询购买状态 // Logan.Li func (s *OrderService) CheckPurchaseStatus(customerEmail string, productID uint) (*dto.CheckPurchaseStatusResponse, error) { var customer model.Customer if err := s.db.Where("email = ?", customerEmail).First(&customer).Error; err != nil { return &dto.CheckPurchaseStatusResponse{ Purchased: false, HasActiveLicense: false, Message: "未找到购买记录", }, nil } var ent model.CustomerEntitlement if err := s.db.Where("customer_id = ? AND product_id = ?", customer.ID, productID). Preload("Plan").First(&ent).Error; err != nil { return &dto.CheckPurchaseStatusResponse{ Purchased: false, HasActiveLicense: false, Message: "未购买该产品", }, nil } daysLeft := 0 if ent.ValidUntil != nil { daysLeft = utils.CalculateDaysLeft(*ent.ValidUntil) } // 查询 License 设备数 // Logan.Li var lic model.License var currentDevices int64 licenseStatus := "" maxDevices := 0 if ent.LicenseID > 0 { s.db.First(&lic, ent.LicenseID) licenseStatus = lic.Status maxDevices = lic.MaxActivations s.db.Model(&model.Activation{}).Where("license_id = ?", lic.ID).Count(¤tDevices) } isActive := ent.Status == "active" && (ent.ValidUntil == nil || daysLeft > 0) var validUntilStr *string if ent.ValidUntil != nil { s := ent.ValidUntil.Format("2006-01-02") validUntilStr = &s } return &dto.CheckPurchaseStatusResponse{ Purchased: true, HasActiveLicense: isActive, LicenseKey: ent.LicenseKey, LicenseStatus: licenseStatus, PlanType: ent.PlanType, ValidUntil: validUntilStr, DaysLeft: daysLeft, Renewable: ent.Renewable, MaxDevices: maxDevices, CurrentDevices: int(currentDevices), EntitlementID: ent.ID, }, nil } // toOrderResponse 模型转响应 DTO // Logan.Li func toOrderResponse(o model.Order) dto.OrderResponse { resp := dto.OrderResponse{ ID: o.ID, OrderNo: o.OrderNo, CustomerID: o.CustomerID, ProductID: o.ProductID, PlanID: o.PlanID, LicenseID: o.LicenseID, Amount: o.Amount, Currency: o.Currency, Status: o.Status, PaymentGateway: o.PaymentGateway, CreatedAt: o.CreatedAt.Format("2006-01-02 15:04:05"), } if o.Customer.ID > 0 { resp.CustomerName = o.Customer.Name } if o.Product.ID > 0 { resp.ProductName = o.Product.Name } if o.Plan.ID > 0 { resp.PlanName = o.Plan.Name } if o.PaidAt != nil { resp.PaidAt = o.PaidAt.Format("2006-01-02 15:04:05") } return resp }