add购买记录功能
This commit is contained in:
@@ -1292,7 +1292,7 @@ func (s *ApiApplicationServiceImpl) UpdateUserBalanceAlertSettings(ctx context.C
|
||||
// TestBalanceAlertSms 测试余额预警短信
|
||||
func (s *ApiApplicationServiceImpl) TestBalanceAlertSms(ctx context.Context, userID string, phone string, balance float64, alertType string) error {
|
||||
// 获取用户信息以获取企业名称
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户信息失败",
|
||||
zap.String("user_id", userID),
|
||||
|
||||
@@ -125,3 +125,45 @@ type UserSimpleResponse struct {
|
||||
CompanyName string `json:"company_name"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
// PurchaseRecordResponse 购买记录响应
|
||||
type PurchaseRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
TradeNo *string `json:"trade_no,omitempty"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
ProductName string `json:"product_name"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Subject string `json:"subject"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
PayAmount *decimal.Decimal `json:"pay_amount,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Platform string `json:"platform"`
|
||||
PayChannel string `json:"pay_channel"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
BuyerID string `json:"buyer_id,omitempty"`
|
||||
SellerID string `json:"seller_id,omitempty"`
|
||||
ReceiptAmount decimal.Decimal `json:"receipt_amount,omitempty"`
|
||||
NotifyTime *time.Time `json:"notify_time,omitempty"`
|
||||
ReturnTime *time.Time `json:"return_time,omitempty"`
|
||||
PayTime *time.Time `json:"pay_time,omitempty"`
|
||||
FilePath *string `json:"file_path,omitempty"`
|
||||
FileSize *int64 `json:"file_size,omitempty"`
|
||||
Remark string `json:"remark,omitempty"`
|
||||
ErrorCode string `json:"error_code,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// PurchaseRecordListResponse 购买记录列表响应
|
||||
type PurchaseRecordListResponse struct {
|
||||
Items []PurchaseRecordResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ type FinanceApplicationService interface {
|
||||
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||
|
||||
// 购买记录
|
||||
GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
|
||||
GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
|
||||
|
||||
// 获取充值配置
|
||||
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_repositories "tyapi-server/internal/domains/product/repositories"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/shared/component_report"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/export"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
@@ -36,6 +36,7 @@ type FinanceApplicationServiceImpl struct {
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||
wechatOrderRepo finance_repositories.WechatOrderRepository
|
||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
||||
purchaseOrderRepo finance_repositories.PurchaseOrderRepository
|
||||
componentReportRepo product_repositories.ComponentReportRepository
|
||||
userRepo user_repositories.UserRepository
|
||||
txManager *database.TransactionManager
|
||||
@@ -54,6 +55,7 @@ func NewFinanceApplicationService(
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
||||
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
||||
purchaseOrderRepo finance_repositories.PurchaseOrderRepository,
|
||||
componentReportRepo product_repositories.ComponentReportRepository,
|
||||
userRepo user_repositories.UserRepository,
|
||||
txManager *database.TransactionManager,
|
||||
@@ -70,6 +72,7 @@ func NewFinanceApplicationService(
|
||||
alipayOrderRepo: alipayOrderRepo,
|
||||
wechatOrderRepo: wechatOrderRepo,
|
||||
rechargeRecordRepo: rechargeRecordRepo,
|
||||
purchaseOrderRepo: purchaseOrderRepo,
|
||||
componentReportRepo: componentReportRepo,
|
||||
userRepo: userRepo,
|
||||
txManager: txManager,
|
||||
@@ -854,13 +857,7 @@ func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context
|
||||
zap.String("trade_no", notification.TradeNo),
|
||||
)
|
||||
|
||||
// 先检查是否是组件报告下载的支付订单
|
||||
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
|
||||
zap.String("out_trade_no", notification.OutTradeNo),
|
||||
)
|
||||
|
||||
// 使用公共方法处理支付成功逻辑(包括更新充值记录状态)
|
||||
// 无论是组件报告下载订单还是普通充值订单,都需要更新充值记录状态
|
||||
// 处理支付宝支付成功逻辑
|
||||
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝支付成功失败",
|
||||
@@ -886,20 +883,52 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
|
||||
return err
|
||||
}
|
||||
|
||||
// 直接调用充值记录服务处理支付成功逻辑
|
||||
// 该服务内部会处理所有必要的检查、事务和更新操作
|
||||
// 如果是组件报告下载订单,服务会自动跳过钱包余额增加
|
||||
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
||||
// 查找支付宝订单
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查并更新组件报告下载记录状态(如果存在)
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
if alipayOrder == nil {
|
||||
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||
return fmt.Errorf("支付宝订单不存在")
|
||||
}
|
||||
|
||||
// 判断是否为充值订单还是购买订单
|
||||
_, err = s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||
if err == nil {
|
||||
// 这是充值订单,调用充值记录服务处理支付成功逻辑
|
||||
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝充值支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 尝试查找购买订单
|
||||
_, err = s.purchaseOrderRepo.GetByID(ctx, alipayOrder.RechargeID)
|
||||
if err == nil {
|
||||
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
|
||||
err = s.processPurchaseOrderPaymentSuccess(ctx, alipayOrder.RechargeID, tradeNo, amount, buyerID, sellerID)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝购买订单支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("purchase_order_id", alipayOrder.RechargeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
s.logger.Error("无法确定订单类型",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", alipayOrder.RechargeID),
|
||||
)
|
||||
return fmt.Errorf("无法确定订单类型")
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("支付宝支付成功处理完成",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
@@ -1477,30 +1506,7 @@ func (s *FinanceApplicationServiceImpl) HandleWechatPayCallback(ctx context.Cont
|
||||
zap.String("transaction_id", transactionID),
|
||||
)
|
||||
|
||||
// 先检查是否是组件报告下载的支付订单
|
||||
s.logger.Info("步骤1: 检查是否是组件报告下载订单",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
// 检查组件报告下载记录
|
||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
|
||||
if err == nil && download != nil {
|
||||
s.logger.Info("步骤2: 发现组件报告下载订单,直接更新下载记录状态",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("current_status", download.PaymentStatus),
|
||||
)
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
s.logger.Info("========== 组件报告下载订单处理完成 ==========")
|
||||
return nil
|
||||
}
|
||||
|
||||
s.logger.Info("步骤3: 不是组件报告下载订单,按充值流程处理",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
// 处理支付成功逻辑(充值流程)
|
||||
// 处理微信支付成功逻辑(充值流程)
|
||||
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("处理微信支付成功失败",
|
||||
@@ -1535,26 +1541,34 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
return fmt.Errorf("微信订单不存在")
|
||||
}
|
||||
|
||||
// 查找对应的充值记录
|
||||
// 判断是否为充值订单还是购买订单
|
||||
rechargeRecord, err := s.rechargeRecordService.GetByID(ctx, wechatOrder.RechargeID)
|
||||
if err != nil {
|
||||
s.logger.Error("查找充值记录失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", wechatOrder.RechargeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("查找充值记录失败: %w", err)
|
||||
if err == nil {
|
||||
// 这是充值订单,继续原有的处理逻辑
|
||||
} else {
|
||||
// 尝试查找购买订单
|
||||
_, err = s.purchaseOrderRepo.GetByID(ctx, wechatOrder.RechargeID)
|
||||
if err == nil {
|
||||
// 这是购买订单(可能是示例报告购买订单),调用处理购买订单支付成功逻辑
|
||||
err = s.processPurchaseOrderPaymentSuccess(ctx, wechatOrder.RechargeID, transactionID, amount, "", "")
|
||||
if err != nil {
|
||||
s.logger.Error("处理微信购买订单支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("purchase_order_id", wechatOrder.RechargeID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
s.logger.Error("无法确定订单类型",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", wechatOrder.RechargeID),
|
||||
)
|
||||
return fmt.Errorf("无法确定订单类型")
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("步骤4: 检查充值记录备注,判断是否为组件报告下载订单",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
zap.String("notes", rechargeRecord.Notes),
|
||||
)
|
||||
|
||||
// 检查是否是组件报告下载订单(通过备注判断)
|
||||
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
|
||||
|
||||
// 检查订单和充值记录状态,如果都已成功则跳过(只记录一次日志)
|
||||
if wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
|
||||
s.logger.Info("微信支付订单已处理成功,跳过重复处理",
|
||||
@@ -1562,12 +1576,7 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
zap.String("transaction_id", transactionID),
|
||||
zap.String("order_id", wechatOrder.ID),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
zap.Bool("is_component_report", isComponentReportOrder),
|
||||
)
|
||||
// 如果是组件报告下载订单,确保更新下载记录状态
|
||||
if isComponentReportOrder {
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1638,33 +1647,17 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
)
|
||||
}
|
||||
|
||||
// 检查是否是组件报告下载订单(通过备注判断)
|
||||
isComponentReportOrder := strings.Contains(rechargeRecord.Notes, "购买") && strings.Contains(rechargeRecord.Notes, "报告示例")
|
||||
|
||||
if isComponentReportOrder {
|
||||
s.logger.Info("步骤5: 检测到组件报告下载订单,不增加钱包余额",
|
||||
// 充值到钱包(包含赠送金额)
|
||||
totalRechargeAmount := amount.Add(bonusAmount)
|
||||
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("充值到钱包失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
zap.String("notes", rechargeRecord.Notes),
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
zap.String("total_amount", totalRechargeAmount.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
// 组件报告下载订单不增加钱包余额,只更新订单和充值记录状态
|
||||
} else {
|
||||
s.logger.Info("步骤5: 普通充值订单,增加钱包余额",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("recharge_id", rechargeRecord.ID),
|
||||
)
|
||||
// 充值到钱包(包含赠送金额)
|
||||
totalRechargeAmount := amount.Add(bonusAmount)
|
||||
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("充值到钱包失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
zap.String("total_amount", totalRechargeAmount.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1680,105 +1673,129 @@ func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是组件报告下载订单,更新下载记录状态
|
||||
if isComponentReportOrder {
|
||||
s.logger.Info("步骤6: 更新组件报告下载记录状态",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
s.updateComponentReportDownloadStatus(ctx, outTradeNo)
|
||||
}
|
||||
|
||||
s.logger.Info("微信支付成功处理完成",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("transaction_id", transactionID),
|
||||
zap.String("amount", amount.String()),
|
||||
zap.String("bonus_amount", bonusAmount.String()),
|
||||
zap.String("user_id", rechargeRecord.UserID),
|
||||
zap.Bool("is_component_report", isComponentReportOrder),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateComponentReportDownloadStatus 更新组件报告下载记录状态
|
||||
func (s *FinanceApplicationServiceImpl) updateComponentReportDownloadStatus(ctx context.Context, outTradeNo string) {
|
||||
s.logger.Info("========== 开始更新组件报告下载记录状态 ==========",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
if s.componentReportRepo == nil {
|
||||
s.logger.Warn("组件报告下载Repository未初始化,跳过更新")
|
||||
return
|
||||
}
|
||||
|
||||
// 根据支付订单号查找组件报告下载记录
|
||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, outTradeNo)
|
||||
// processPurchaseOrderPaymentSuccess 处理购买订单支付成功的逻辑
|
||||
func (s *FinanceApplicationServiceImpl) processPurchaseOrderPaymentSuccess(ctx context.Context, purchaseOrderID, tradeNo string, amount decimal.Decimal, buyerID, sellerID string) error {
|
||||
// 查找购买订单
|
||||
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, purchaseOrderID)
|
||||
if err != nil {
|
||||
s.logger.Info("未找到组件报告下载记录,可能不是组件报告下载订单",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
s.logger.Error("查找购买订单失败",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
return fmt.Errorf("查找购买订单失败: %w", err)
|
||||
}
|
||||
|
||||
if download == nil {
|
||||
s.logger.Info("组件报告下载记录为空,跳过更新",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
if purchaseOrder == nil {
|
||||
s.logger.Error("购买订单不存在",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
)
|
||||
return
|
||||
return fmt.Errorf("购买订单不存在")
|
||||
}
|
||||
|
||||
s.logger.Info("步骤1: 找到组件报告下载记录",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("current_status", download.PaymentStatus),
|
||||
)
|
||||
|
||||
// 如果已经是成功状态,跳过
|
||||
if download.PaymentStatus == "success" {
|
||||
s.logger.Info("组件报告下载记录已是成功状态,跳过更新",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
// 检查订单状态,如果已支付则跳过
|
||||
if purchaseOrder.Status == finance_entities.PurchaseOrderStatusPaid {
|
||||
s.logger.Info("购买订单已支付,跳过处理",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
s.logger.Info("步骤2: 更新支付状态为成功",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
)
|
||||
|
||||
// 更新支付状态为成功
|
||||
download.PaymentStatus = "success"
|
||||
|
||||
// 设置过期时间(30天后)
|
||||
expiresAt := time.Now().Add(30 * 24 * time.Hour)
|
||||
download.ExpiresAt = &expiresAt
|
||||
|
||||
s.logger.Info("步骤3: 保存更新后的下载记录",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("expires_at", expiresAt.Format("2006-01-02 15:04:05")),
|
||||
)
|
||||
|
||||
// 更新记录
|
||||
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
||||
// 更新购买订单状态
|
||||
purchaseOrder.MarkPaid(tradeNo, buyerID, sellerID, amount, amount)
|
||||
err = s.purchaseOrderRepo.Update(ctx, purchaseOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新组件报告下载记录状态失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
s.logger.Error("更新购买订单状态失败",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
return fmt.Errorf("更新购买订单状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("========== 组件报告下载记录状态更新成功 ==========",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("payment_status", download.PaymentStatus),
|
||||
// 更新对应的支付订单状态(微信或支付宝)
|
||||
if purchaseOrder.PayChannel == "alipay" {
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
|
||||
if err == nil && alipayOrder != nil {
|
||||
alipayOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
|
||||
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新支付宝订单状态失败",
|
||||
zap.String("out_trade_no", alipayOrder.OutTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if purchaseOrder.PayChannel == "wechat" {
|
||||
wechatOrder, err := s.wechatOrderRepo.GetByRechargeID(ctx, purchaseOrderID)
|
||||
if err == nil && wechatOrder != nil {
|
||||
wechatOrder.MarkSuccess(tradeNo, buyerID, sellerID, amount, amount)
|
||||
err = s.wechatOrderRepo.Update(ctx, *wechatOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新微信订单状态失败",
|
||||
zap.String("out_trade_no", wechatOrder.OutTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是组件报告购买,需要生成并更新报告文件
|
||||
download, err := s.componentReportRepo.GetDownloadByPaymentOrderID(ctx, purchaseOrderID)
|
||||
if err == nil && download != nil {
|
||||
// 创建报告生成器
|
||||
zipGenerator := component_report.NewZipGenerator(s.logger)
|
||||
|
||||
// 生成报告文件
|
||||
zipPath, err := zipGenerator.GenerateZipFile(
|
||||
ctx,
|
||||
download.ProductID,
|
||||
[]string{download.ProductCode}, // 使用简化后的只包含主产品编号的列表
|
||||
nil, // 使用默认的JSON生成器
|
||||
"", // 使用默认路径
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("生成组件报告文件失败",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.Error(err),
|
||||
)
|
||||
// 不中断流程,即使生成文件失败也继续处理
|
||||
} else {
|
||||
// 更新下载记录的文件路径
|
||||
download.FilePath = &zipPath
|
||||
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
||||
if err != nil {
|
||||
s.logger.Error("更新下载记录文件路径失败",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
s.logger.Info("组件报告文件生成成功",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("file_path", zipPath),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("购买订单支付成功处理完成",
|
||||
zap.String("purchase_order_id", purchaseOrderID),
|
||||
zap.String("trade_no", tradeNo),
|
||||
zap.String("amount", amount.String()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleWechatRefundCallback 处理微信退款回调
|
||||
@@ -1842,3 +1859,163 @@ func (s *FinanceApplicationServiceImpl) HandleWechatRefundCallback(ctx context.C
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserPurchaseRecords 获取用户购买记录
|
||||
func (s *FinanceApplicationServiceImpl) GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
|
||||
// 确保 filters 不为 nil
|
||||
if filters == nil {
|
||||
filters = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
|
||||
filters["user_id"] = userID
|
||||
|
||||
// 获取总数
|
||||
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
|
||||
if err != nil {
|
||||
s.logger.Error("统计用户购买记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询用户购买记录(使用筛选和分页功能)
|
||||
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询用户购买记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.PurchaseRecordResponse
|
||||
for _, order := range orders {
|
||||
item := responses.PurchaseRecordResponse{
|
||||
ID: order.ID,
|
||||
UserID: order.UserID,
|
||||
OrderNo: order.OrderNo,
|
||||
TradeNo: order.TradeNo,
|
||||
ProductID: order.ProductID,
|
||||
ProductCode: order.ProductCode,
|
||||
ProductName: order.ProductName,
|
||||
Category: order.Category,
|
||||
Subject: order.Subject,
|
||||
Amount: order.Amount,
|
||||
PayAmount: order.PayAmount,
|
||||
Status: string(order.Status),
|
||||
Platform: order.Platform,
|
||||
PayChannel: order.PayChannel,
|
||||
PaymentType: order.PaymentType,
|
||||
BuyerID: order.BuyerID,
|
||||
SellerID: order.SellerID,
|
||||
ReceiptAmount: order.ReceiptAmount,
|
||||
NotifyTime: order.NotifyTime,
|
||||
ReturnTime: order.ReturnTime,
|
||||
PayTime: order.PayTime,
|
||||
FilePath: order.FilePath,
|
||||
FileSize: order.FileSize,
|
||||
Remark: order.Remark,
|
||||
ErrorCode: order.ErrorCode,
|
||||
ErrorMessage: order.ErrorMessage,
|
||||
CreatedAt: order.CreatedAt,
|
||||
UpdatedAt: order.UpdatedAt,
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = companyName
|
||||
item.User = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.PurchaseRecordListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAdminPurchaseRecords 获取管理端购买记录
|
||||
func (s *FinanceApplicationServiceImpl) GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error) {
|
||||
// 获取总数
|
||||
total, err := s.purchaseOrderRepo.CountByFilters(ctx, filters)
|
||||
if err != nil {
|
||||
s.logger.Error("统计管理端购买记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查询购买记录
|
||||
orders, err := s.purchaseOrderRepo.GetByFilters(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询管理端购买记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.PurchaseRecordResponse
|
||||
for _, order := range orders {
|
||||
item := responses.PurchaseRecordResponse{
|
||||
ID: order.ID,
|
||||
UserID: order.UserID,
|
||||
OrderNo: order.OrderNo,
|
||||
TradeNo: order.TradeNo,
|
||||
ProductID: order.ProductID,
|
||||
ProductCode: order.ProductCode,
|
||||
ProductName: order.ProductName,
|
||||
Category: order.Category,
|
||||
Subject: order.Subject,
|
||||
Amount: order.Amount,
|
||||
PayAmount: order.PayAmount,
|
||||
Status: string(order.Status),
|
||||
Platform: order.Platform,
|
||||
PayChannel: order.PayChannel,
|
||||
PaymentType: order.PaymentType,
|
||||
BuyerID: order.BuyerID,
|
||||
SellerID: order.SellerID,
|
||||
ReceiptAmount: order.ReceiptAmount,
|
||||
NotifyTime: order.NotifyTime,
|
||||
ReturnTime: order.ReturnTime,
|
||||
PayTime: order.PayTime,
|
||||
FilePath: order.FilePath,
|
||||
FileSize: order.FileSize,
|
||||
Remark: order.Remark,
|
||||
ErrorCode: order.ErrorCode,
|
||||
ErrorMessage: order.ErrorMessage,
|
||||
CreatedAt: order.CreatedAt,
|
||||
UpdatedAt: order.UpdatedAt,
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, order.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = companyName
|
||||
item.User = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.PurchaseRecordListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
752
internal/application/product/component_report_order_service.go
Normal file
752
internal/application/product/component_report_order_service.go
Normal file
@@ -0,0 +1,752 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
financeRepositories "tyapi-server/internal/domains/finance/repositories"
|
||||
productEntities "tyapi-server/internal/domains/product/entities"
|
||||
productRepositories "tyapi-server/internal/domains/product/repositories"
|
||||
"tyapi-server/internal/shared/component_report"
|
||||
"tyapi-server/internal/shared/payment"
|
||||
)
|
||||
|
||||
// ComponentReportOrderService 组件报告订单服务
|
||||
type ComponentReportOrderService struct {
|
||||
productRepo productRepositories.ProductRepository
|
||||
docRepo productRepositories.ProductDocumentationRepository
|
||||
apiConfigRepo productRepositories.ProductApiConfigRepository
|
||||
purchaseOrderRepo financeRepositories.PurchaseOrderRepository
|
||||
componentReportRepo productRepositories.ComponentReportRepository
|
||||
rechargeRecordRepo financeRepositories.RechargeRecordRepository
|
||||
alipayOrderRepo financeRepositories.AlipayOrderRepository
|
||||
wechatOrderRepo financeRepositories.WechatOrderRepository
|
||||
aliPayService *payment.AliPayService
|
||||
wechatPayService *payment.WechatPayService
|
||||
exampleJSONGenerator *component_report.ExampleJSONGenerator
|
||||
zipGenerator *component_report.ZipGenerator
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewComponentReportOrderService 创建组件报告订单服务
|
||||
func NewComponentReportOrderService(
|
||||
productRepo productRepositories.ProductRepository,
|
||||
docRepo productRepositories.ProductDocumentationRepository,
|
||||
apiConfigRepo productRepositories.ProductApiConfigRepository,
|
||||
purchaseOrderRepo financeRepositories.PurchaseOrderRepository,
|
||||
componentReportRepo productRepositories.ComponentReportRepository,
|
||||
rechargeRecordRepo financeRepositories.RechargeRecordRepository,
|
||||
alipayOrderRepo financeRepositories.AlipayOrderRepository,
|
||||
wechatOrderRepo financeRepositories.WechatOrderRepository,
|
||||
aliPayService *payment.AliPayService,
|
||||
wechatPayService *payment.WechatPayService,
|
||||
logger *zap.Logger,
|
||||
) *ComponentReportOrderService {
|
||||
exampleJSONGenerator := component_report.NewExampleJSONGenerator(productRepo, docRepo, apiConfigRepo, logger)
|
||||
zipGenerator := component_report.NewZipGenerator(logger)
|
||||
|
||||
return &ComponentReportOrderService{
|
||||
productRepo: productRepo,
|
||||
docRepo: docRepo,
|
||||
apiConfigRepo: apiConfigRepo,
|
||||
purchaseOrderRepo: purchaseOrderRepo,
|
||||
componentReportRepo: componentReportRepo,
|
||||
rechargeRecordRepo: rechargeRecordRepo,
|
||||
alipayOrderRepo: alipayOrderRepo,
|
||||
wechatOrderRepo: wechatOrderRepo,
|
||||
aliPayService: aliPayService,
|
||||
wechatPayService: wechatPayService,
|
||||
exampleJSONGenerator: exampleJSONGenerator,
|
||||
zipGenerator: zipGenerator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOrderInfo 获取订单信息
|
||||
func (s *ComponentReportOrderService) GetOrderInfo(ctx context.Context, userID, productID string) (*OrderInfo, error) {
|
||||
s.logger.Info("开始获取订单信息", zap.String("user_id", userID), zap.String("product_id", productID))
|
||||
|
||||
// 获取产品信息
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品信息失败", zap.Error(err), zap.String("product_id", productID))
|
||||
return nil, fmt.Errorf("获取产品信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("获取产品信息成功",
|
||||
zap.String("product_id", product.ID),
|
||||
zap.String("product_code", product.Code),
|
||||
zap.String("product_name", product.Name),
|
||||
zap.Bool("is_package", product.IsPackage),
|
||||
zap.String("price", product.Price.String()),
|
||||
)
|
||||
|
||||
// 检查是否为组合包
|
||||
if !product.IsPackage {
|
||||
s.logger.Error("产品不是组合包", zap.String("product_id", productID), zap.String("product_code", product.Code))
|
||||
return nil, fmt.Errorf("只有组合包产品才能下载示例报告")
|
||||
}
|
||||
|
||||
// 获取组合包子产品
|
||||
packageItems, err := s.productRepo.GetPackageItems(ctx, productID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取组合包子产品失败", zap.Error(err), zap.String("product_id", productID))
|
||||
return nil, fmt.Errorf("获取组合包子产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("获取组合包子产品成功",
|
||||
zap.String("product_id", productID),
|
||||
zap.Int("package_items_count", len(packageItems)),
|
||||
)
|
||||
|
||||
// 获取用户已购买的产品编号列表
|
||||
purchasedCodes, err := s.purchaseOrderRepo.GetUserPurchasedProductCodes(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取用户已购买产品编号失败", zap.Error(err), zap.String("user_id", userID))
|
||||
purchasedCodes = []string{}
|
||||
}
|
||||
|
||||
s.logger.Info("获取用户已购买产品编号列表",
|
||||
zap.String("user_id", userID),
|
||||
zap.Strings("purchased_codes", purchasedCodes),
|
||||
zap.Int("purchased_count", len(purchasedCodes)),
|
||||
)
|
||||
|
||||
// 创建已购买编号的map用于快速查找
|
||||
purchasedMap := make(map[string]bool)
|
||||
for _, code := range purchasedCodes {
|
||||
purchasedMap[code] = true
|
||||
}
|
||||
|
||||
// 使用产品的UIComponentPrice作为最终价格
|
||||
finalPrice := product.UIComponentPrice
|
||||
s.logger.Info("使用UI组件价格",
|
||||
zap.String("product_id", productID),
|
||||
zap.String("product_ui_component_price", finalPrice.String()),
|
||||
)
|
||||
|
||||
// 准备子产品信息列表(仅用于展示,不参与价格计算)
|
||||
var subProducts []SubProductPriceInfo
|
||||
for _, item := range packageItems {
|
||||
var subProduct productEntities.Product
|
||||
var productCode string
|
||||
var productName string
|
||||
var price decimal.Decimal
|
||||
|
||||
if item.Product != nil {
|
||||
subProduct = *item.Product
|
||||
productCode = subProduct.Code
|
||||
productName = subProduct.Name
|
||||
price = subProduct.Price
|
||||
} else {
|
||||
subProduct, err = s.productRepo.GetByID(ctx, item.ProductID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取子产品信息失败", zap.Error(err), zap.String("product_id", item.ProductID))
|
||||
continue
|
||||
}
|
||||
productCode = subProduct.Code
|
||||
productName = subProduct.Name
|
||||
price = subProduct.Price
|
||||
}
|
||||
|
||||
if productCode == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否已购买
|
||||
isPurchased := purchasedMap[productCode]
|
||||
|
||||
subProducts = append(subProducts, SubProductPriceInfo{
|
||||
ProductID: subProduct.ID,
|
||||
ProductCode: productCode,
|
||||
ProductName: productName,
|
||||
Price: price.String(),
|
||||
IsPurchased: isPurchased,
|
||||
})
|
||||
}
|
||||
|
||||
// 检查用户是否有已支付的下载记录(针对当前产品)
|
||||
hasPaidDownload := false
|
||||
orders, _, err := s.purchaseOrderRepo.GetByUserID(ctx, userID, 100, 0)
|
||||
if err == nil {
|
||||
s.logger.Info("检查用户已支付的下载记录",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("product_id", productID),
|
||||
zap.Int("orders_count", len(orders)),
|
||||
)
|
||||
|
||||
for _, order := range orders {
|
||||
if order.ProductID == productID && order.Status == entities.PurchaseOrderStatusPaid {
|
||||
hasPaidDownload = true
|
||||
s.logger.Info("找到有效的已支付下载记录",
|
||||
zap.String("order_id", order.ID),
|
||||
zap.String("order_no", order.OrderNo),
|
||||
zap.String("product_id", order.ProductID),
|
||||
zap.String("purchase_status", string(order.Status)),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.logger.Warn("获取用户订单失败", zap.Error(err), zap.String("user_id", userID))
|
||||
}
|
||||
|
||||
// 如果可以下载:价格为0(免费)或者用户已支付
|
||||
canDownload := finalPrice.IsZero() || hasPaidDownload
|
||||
|
||||
s.logger.Info("最终订单信息",
|
||||
zap.String("product_id", productID),
|
||||
zap.String("product_code", product.Code),
|
||||
zap.String("product_name", product.Name),
|
||||
zap.Int("sub_products_count", len(subProducts)),
|
||||
zap.String("price", finalPrice.String()),
|
||||
zap.Strings("purchased_product_codes", purchasedCodes),
|
||||
zap.Bool("has_paid_download", hasPaidDownload),
|
||||
zap.Bool("can_download", canDownload),
|
||||
)
|
||||
|
||||
// 记录每个子产品的信息
|
||||
for i, subProduct := range subProducts {
|
||||
s.logger.Info("子产品详情",
|
||||
zap.Int("index", i),
|
||||
zap.String("sub_product_id", subProduct.ProductID),
|
||||
zap.String("sub_product_code", subProduct.ProductCode),
|
||||
zap.String("sub_product_name", subProduct.ProductName),
|
||||
zap.String("price", subProduct.Price),
|
||||
zap.Bool("is_purchased", subProduct.IsPurchased),
|
||||
)
|
||||
}
|
||||
|
||||
return &OrderInfo{
|
||||
ProductID: productID,
|
||||
ProductCode: product.Code,
|
||||
ProductName: product.Name,
|
||||
IsPackage: true,
|
||||
SubProducts: subProducts,
|
||||
Price: finalPrice.String(),
|
||||
PurchasedProductCodes: purchasedCodes,
|
||||
CanDownload: canDownload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreatePaymentOrder 创建支付订单
|
||||
func (s *ComponentReportOrderService) CreatePaymentOrder(ctx context.Context, req *CreatePaymentOrderRequest) (*CreatePaymentOrderResponse, error) {
|
||||
// 获取产品信息
|
||||
product, err := s.productRepo.GetByID(ctx, req.ProductID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取产品信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否为组合包
|
||||
if !product.IsPackage {
|
||||
return nil, fmt.Errorf("只有组合包产品才能下载示例报告")
|
||||
}
|
||||
|
||||
// 获取组合包子产品
|
||||
packageItems, err := s.productRepo.GetPackageItems(ctx, req.ProductID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取组合包子产品失败: %w", err)
|
||||
}
|
||||
|
||||
// 使用产品的UIComponentPrice作为价格
|
||||
finalPrice := product.UIComponentPrice
|
||||
s.logger.Info("使用UI组件价格创建支付订单",
|
||||
zap.String("product_id", req.ProductID),
|
||||
zap.String("product_ui_component_price", finalPrice.String()),
|
||||
)
|
||||
|
||||
// 检查价格是否为0
|
||||
if finalPrice.IsZero() {
|
||||
return s.createFreeOrder(ctx, req, &product, nil, nil, finalPrice)
|
||||
}
|
||||
|
||||
// 准备子产品信息列表(仅用于展示)
|
||||
var subProductCodes []string
|
||||
var subProductIDs []string
|
||||
|
||||
for _, item := range packageItems {
|
||||
var subProduct productEntities.Product
|
||||
var productCode string
|
||||
|
||||
if item.Product != nil {
|
||||
subProduct = *item.Product
|
||||
productCode = subProduct.Code
|
||||
} else {
|
||||
subProduct, err = s.productRepo.GetByID(ctx, item.ProductID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
productCode = subProduct.Code
|
||||
}
|
||||
|
||||
if productCode == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 收集所有子产品信息
|
||||
subProductCodes = append(subProductCodes, productCode)
|
||||
subProductIDs = append(subProductIDs, subProduct.ID)
|
||||
}
|
||||
|
||||
// 生成商户订单号
|
||||
var outTradeNo string
|
||||
if req.PaymentType == "alipay" {
|
||||
outTradeNo = s.aliPayService.GenerateOutTradeNo()
|
||||
} else {
|
||||
outTradeNo = s.wechatPayService.GenerateOutTradeNo()
|
||||
}
|
||||
|
||||
// 创建购买订单 - 设置为待支付状态
|
||||
purchaseOrder := entities.NewPurchaseOrder(
|
||||
req.UserID,
|
||||
req.ProductID,
|
||||
product.Code,
|
||||
product.Name,
|
||||
fmt.Sprintf("组件报告下载-%s", product.Name),
|
||||
finalPrice,
|
||||
req.Platform, // 使用传入的平台参数
|
||||
req.PaymentType,
|
||||
req.PaymentType,
|
||||
)
|
||||
|
||||
// 设置为待支付状态
|
||||
purchaseOrder.Status = entities.PurchaseOrderStatusCreated
|
||||
|
||||
createdPurchaseOrder, err := s.purchaseOrderRepo.Create(ctx, purchaseOrder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建购买订单失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建相应的支付记录(未支付状态)
|
||||
if req.PaymentType == "alipay" {
|
||||
// 使用工厂方法创建支付宝订单
|
||||
alipayOrder := entities.NewAlipayOrder(createdPurchaseOrder.ID, outTradeNo,
|
||||
fmt.Sprintf("组件报告下载-%s", product.Name), finalPrice, req.Platform)
|
||||
// 设置为待支付状态
|
||||
alipayOrder.Status = entities.AlipayOrderStatusPending
|
||||
|
||||
_, err = s.alipayOrderRepo.Create(ctx, *alipayOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("创建支付宝订单记录失败", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
// 使用工厂方法创建微信订单
|
||||
wechatOrder := entities.NewWechatOrder(createdPurchaseOrder.ID, outTradeNo,
|
||||
fmt.Sprintf("组件报告下载-%s", product.Name), finalPrice, req.Platform)
|
||||
// 设置为待支付状态
|
||||
wechatOrder.Status = entities.WechatOrderStatusPending
|
||||
|
||||
_, err = s.wechatOrderRepo.Create(ctx, *wechatOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("创建微信订单记录失败", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// 调用真实支付接口创建支付订单
|
||||
var payURL string
|
||||
var codeURL string
|
||||
|
||||
if req.PaymentType == "alipay" {
|
||||
// 调用支付宝支付接口
|
||||
payURL, err = s.aliPayService.CreateAlipayOrder(ctx, req.Platform, finalPrice,
|
||||
fmt.Sprintf("组件报告下载-%s", product.Name), outTradeNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建支付宝支付订单失败: %w", err)
|
||||
}
|
||||
} else if req.PaymentType == "wechat" {
|
||||
// 调用微信支付接口
|
||||
floatValue, _ := finalPrice.Float64() // 忽略第二个返回值
|
||||
result, err := s.wechatPayService.CreateWechatOrder(ctx, floatValue,
|
||||
fmt.Sprintf("组件报告下载-%s", product.Name), outTradeNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建微信支付订单失败: %w", err)
|
||||
}
|
||||
|
||||
// 微信支付返回的是map格式,提取code_url
|
||||
if resultMap, ok := result.(map[string]string); ok {
|
||||
codeURL = resultMap["code_url"]
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个临时下载记录,用于跟踪支付状态,但不生成报告文件
|
||||
download := &productEntities.ComponentReportDownload{
|
||||
UserID: req.UserID,
|
||||
ProductID: req.ProductID,
|
||||
ProductCode: product.Code,
|
||||
ProductName: product.Name,
|
||||
OrderID: &createdPurchaseOrder.ID, // 关联购买订单ID
|
||||
OrderNumber: &outTradeNo, // 外部订单号
|
||||
ExpiresAt: calculateExpiryTime(), // 30天后过期
|
||||
// 注意:这里不设置FilePath,因为文件将在支付成功后生成
|
||||
}
|
||||
|
||||
err = s.componentReportRepo.Create(ctx, download)
|
||||
if err != nil {
|
||||
s.logger.Error("创建下载记录失败", zap.Error(err))
|
||||
// 不中断流程,即使创建下载记录失败也继续返回订单信息
|
||||
}
|
||||
|
||||
// 返回支付响应,包含支付URL
|
||||
response := &CreatePaymentOrderResponse{
|
||||
OrderID: download.ID,
|
||||
OrderNo: createdPurchaseOrder.OrderNo,
|
||||
PaymentType: req.PaymentType,
|
||||
Amount: finalPrice.String(),
|
||||
PayURL: payURL,
|
||||
CodeURL: codeURL,
|
||||
}
|
||||
|
||||
s.logger.Info("支付订单创建成功",
|
||||
zap.String("order_id", download.ID),
|
||||
zap.String("purchase_order_id", createdPurchaseOrder.ID),
|
||||
zap.String("user_id", req.UserID),
|
||||
zap.String("product_id", req.ProductID),
|
||||
zap.String("payment_type", req.PaymentType),
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// createFreeOrder 创建免费订单
|
||||
func (s *ComponentReportOrderService) createFreeOrder(
|
||||
ctx context.Context,
|
||||
req *CreatePaymentOrderRequest,
|
||||
product *productEntities.Product,
|
||||
subProductCodes []string,
|
||||
subProductIDs []string,
|
||||
finalPrice decimal.Decimal,
|
||||
) (*CreatePaymentOrderResponse, error) {
|
||||
// 序列化子产品列表
|
||||
// 简化后的实体不再需要序列化子产品列表
|
||||
|
||||
// 创建免费订单
|
||||
purchaseOrder := entities.NewPurchaseOrder(
|
||||
req.UserID,
|
||||
req.ProductID,
|
||||
product.Code,
|
||||
product.Name,
|
||||
fmt.Sprintf("组件报告下载-%s", product.Name),
|
||||
finalPrice,
|
||||
"app",
|
||||
"free",
|
||||
"free",
|
||||
)
|
||||
|
||||
// 设置为已支付状态
|
||||
purchaseOrder.Status = entities.PurchaseOrderStatusPaid
|
||||
now := time.Now()
|
||||
purchaseOrder.PayTime = &now
|
||||
|
||||
createdPurchaseOrder, err := s.purchaseOrderRepo.Create(ctx, purchaseOrder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建免费订单失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建下载记录
|
||||
download := &productEntities.ComponentReportDownload{
|
||||
UserID: req.UserID,
|
||||
ProductID: req.ProductID,
|
||||
ProductCode: product.Code,
|
||||
ProductName: product.Name,
|
||||
OrderID: &createdPurchaseOrder.ID, // 关联购买订单ID
|
||||
OrderNumber: &createdPurchaseOrder.OrderNo, // 外部订单号
|
||||
ExpiresAt: calculateExpiryTime(), // 30天后过期
|
||||
}
|
||||
|
||||
err = s.componentReportRepo.Create(ctx, download)
|
||||
if err != nil {
|
||||
s.logger.Error("创建下载记录失败", zap.Error(err))
|
||||
// 不中断流程,即使创建下载记录失败也继续返回订单信息
|
||||
}
|
||||
|
||||
return &CreatePaymentOrderResponse{
|
||||
OrderID: download.ID,
|
||||
OrderNo: createdPurchaseOrder.OrderNo,
|
||||
PaymentType: "free",
|
||||
Amount: "0.00",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateReportFile 生成报告文件
|
||||
func (s *ComponentReportOrderService) generateReportFile(ctx context.Context, download *productEntities.ComponentReportDownload) (string, error) {
|
||||
// 解析子产品编号列表
|
||||
// 简化后的实体只使用主产品编号
|
||||
subProductCodes := []string{download.ProductCode}
|
||||
|
||||
// 生成筛选后的组件ZIP文件
|
||||
zipPath, err := s.zipGenerator.GenerateZipFile(
|
||||
ctx,
|
||||
download.ProductID,
|
||||
subProductCodes,
|
||||
s.exampleJSONGenerator,
|
||||
"", // 使用默认路径
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("生成筛选后的组件ZIP文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新下载记录的文件路径
|
||||
download.FilePath = &zipPath
|
||||
err = s.componentReportRepo.UpdateDownload(ctx, download)
|
||||
if err != nil {
|
||||
s.logger.Error("更新下载记录文件信息失败", zap.Error(err), zap.String("download_id", download.ID))
|
||||
// 即使更新失败,也返回文件路径,因为文件已经生成
|
||||
}
|
||||
|
||||
s.logger.Info("报告文件生成成功",
|
||||
zap.String("download_id", download.ID),
|
||||
zap.String("file_path", zipPath),
|
||||
zap.String("product_id", download.ProductID),
|
||||
zap.String("product_code", download.ProductCode))
|
||||
|
||||
return zipPath, nil
|
||||
}
|
||||
|
||||
// CheckPaymentStatus 检查支付状态
|
||||
func (s *ComponentReportOrderService) CheckPaymentStatus(ctx context.Context, orderID string) (*CheckPaymentStatusResponse, error) {
|
||||
// 获取下载记录信息
|
||||
download, err := s.componentReportRepo.GetDownloadByID(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取下载记录信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 使用OrderID查询购买订单状态来判断支付状态
|
||||
var paymentStatus string
|
||||
var canDownload bool
|
||||
|
||||
if download.OrderID != nil {
|
||||
// 查询购买订单状态
|
||||
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, *download.OrderID)
|
||||
if err != nil {
|
||||
s.logger.Error("查询购买订单失败", zap.Error(err), zap.String("order_id", *download.OrderID))
|
||||
paymentStatus = "unknown"
|
||||
} else {
|
||||
// 根据购买订单状态设置支付状态
|
||||
switch purchaseOrder.Status {
|
||||
case entities.PurchaseOrderStatusPaid:
|
||||
paymentStatus = "success"
|
||||
canDownload = true
|
||||
case entities.PurchaseOrderStatusCreated:
|
||||
paymentStatus = "pending"
|
||||
canDownload = false
|
||||
case entities.PurchaseOrderStatusCancelled:
|
||||
paymentStatus = "cancelled"
|
||||
canDownload = false
|
||||
case entities.PurchaseOrderStatusFailed:
|
||||
paymentStatus = "failed"
|
||||
canDownload = false
|
||||
default:
|
||||
paymentStatus = "unknown"
|
||||
canDownload = false
|
||||
}
|
||||
}
|
||||
} else if download.OrderNumber != nil {
|
||||
// 兼容旧的支付订单逻辑
|
||||
paymentStatus = "success" // 简化处理,有支付订单号就认为已支付
|
||||
canDownload = true
|
||||
} else {
|
||||
paymentStatus = "pending"
|
||||
canDownload = false
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if download.IsExpired() {
|
||||
canDownload = false
|
||||
}
|
||||
|
||||
// 返回支付状态
|
||||
return &CheckPaymentStatusResponse{
|
||||
OrderID: download.ID,
|
||||
PaymentStatus: paymentStatus,
|
||||
CanDownload: canDownload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DownloadFile 下载文件
|
||||
func (s *ComponentReportOrderService) DownloadFile(ctx context.Context, orderID string) (string, error) {
|
||||
// 获取下载记录信息
|
||||
download, err := s.componentReportRepo.GetDownloadByID(ctx, orderID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取下载记录信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 使用OrderID查询购买订单状态来判断支付状态
|
||||
var canDownload bool
|
||||
|
||||
if download.OrderID != nil {
|
||||
// 查询购买订单状态
|
||||
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, *download.OrderID)
|
||||
if err != nil {
|
||||
s.logger.Error("查询购买订单失败", zap.Error(err), zap.String("order_id", *download.OrderID))
|
||||
canDownload = false
|
||||
} else {
|
||||
// 检查购买订单状态
|
||||
canDownload = purchaseOrder.Status == entities.PurchaseOrderStatusPaid
|
||||
}
|
||||
} else if download.OrderNumber != nil {
|
||||
// 兼容旧的支付订单逻辑
|
||||
canDownload = true // 简化处理,有支付订单号就认为已支付
|
||||
} else {
|
||||
canDownload = false
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if download.IsExpired() {
|
||||
canDownload = false
|
||||
}
|
||||
|
||||
if !canDownload {
|
||||
return "", fmt.Errorf("订单未支付或已过期,无法下载文件")
|
||||
}
|
||||
|
||||
// 检查文件是否已存在
|
||||
if download.FilePath != nil && *download.FilePath != "" {
|
||||
// 文件已存在,直接返回文件路径
|
||||
s.logger.Info("返回已存在的文件路径",
|
||||
zap.String("order_id", orderID),
|
||||
zap.String("file_path", *download.FilePath))
|
||||
return *download.FilePath, nil
|
||||
}
|
||||
|
||||
// 文件不存在,生成文件
|
||||
filePath, err := s.generateReportFile(ctx, download)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("生成报告文件失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("成功生成报告文件",
|
||||
zap.String("order_id", orderID),
|
||||
zap.String("file_path", filePath))
|
||||
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
// GetUserOrders 获取用户订单列表
|
||||
func (s *ComponentReportOrderService) GetUserOrders(ctx context.Context, userID string, limit, offset int) ([]*UserOrderResponse, int64, error) {
|
||||
// 获取用户的下载记录
|
||||
downloads, err := s.componentReportRepo.GetUserDownloads(ctx, userID, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为响应格式
|
||||
result := make([]*UserOrderResponse, 0, len(downloads))
|
||||
for _, download := range downloads {
|
||||
// 使用OrderID查询购买订单状态来判断支付状态
|
||||
var purchaseStatus string = "pending"
|
||||
var paymentType string = "unknown"
|
||||
var paymentTime *time.Time = nil
|
||||
|
||||
if download.OrderID != nil {
|
||||
// 查询购买订单状态
|
||||
purchaseOrder, err := s.purchaseOrderRepo.GetByID(ctx, *download.OrderID)
|
||||
if err == nil {
|
||||
switch purchaseOrder.Status {
|
||||
case entities.PurchaseOrderStatusPaid:
|
||||
purchaseStatus = "paid"
|
||||
paymentTime = purchaseOrder.PayTime
|
||||
case entities.PurchaseOrderStatusCreated:
|
||||
purchaseStatus = "created"
|
||||
case entities.PurchaseOrderStatusCancelled:
|
||||
purchaseStatus = "cancelled"
|
||||
case entities.PurchaseOrderStatusFailed:
|
||||
purchaseStatus = "failed"
|
||||
}
|
||||
paymentType = purchaseOrder.PayChannel
|
||||
}
|
||||
} else if download.OrderNumber != nil {
|
||||
// 兼容旧的支付订单逻辑
|
||||
purchaseStatus = "paid" // 简化处理,有支付订单号就认为已支付
|
||||
paymentType = "unknown"
|
||||
}
|
||||
|
||||
result = append(result, &UserOrderResponse{
|
||||
ID: download.ID,
|
||||
OrderNo: "",
|
||||
ProductID: download.ProductID,
|
||||
ProductCode: download.ProductCode,
|
||||
PaymentType: paymentType,
|
||||
PurchaseStatus: purchaseStatus,
|
||||
Price: "0.00", // 下载记录不存储价格信息
|
||||
CreatedAt: download.CreatedAt,
|
||||
PaymentTime: paymentTime,
|
||||
})
|
||||
}
|
||||
|
||||
return result, int64(len(result)), nil
|
||||
}
|
||||
|
||||
// calculateExpiryTime 计算下载有效期(从创建日起30天)
|
||||
func calculateExpiryTime() *time.Time {
|
||||
now := time.Now()
|
||||
expiry := now.AddDate(0, 0, 30) // 30天后过期
|
||||
return &expiry
|
||||
}
|
||||
|
||||
// 数据结构定义
|
||||
|
||||
// OrderInfo 订单信息
|
||||
type OrderInfo struct {
|
||||
ProductID string `json:"product_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
ProductName string `json:"product_name"`
|
||||
IsPackage bool `json:"is_package"`
|
||||
SubProducts []SubProductPriceInfo `json:"sub_products"`
|
||||
Price string `json:"price"` // UI组件价格
|
||||
PurchasedProductCodes []string `json:"purchased_product_codes"`
|
||||
CanDownload bool `json:"can_download"`
|
||||
}
|
||||
|
||||
// SubProductPriceInfo 子产品价格信息
|
||||
type SubProductPriceInfo struct {
|
||||
ProductID string `json:"product_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
ProductName string `json:"product_name"`
|
||||
Price string `json:"price"`
|
||||
IsPurchased bool `json:"is_purchased"`
|
||||
}
|
||||
|
||||
// CreatePaymentOrderRequest 创建支付订单请求
|
||||
type CreatePaymentOrderRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
PaymentType string `json:"payment_type"` // wechat 或 alipay
|
||||
Platform string `json:"platform"` // 支付平台:app, h5, pc(可选,默认根据User-Agent判断)
|
||||
SubProductCodes []string `json:"sub_product_codes,omitempty"`
|
||||
}
|
||||
|
||||
// CreatePaymentOrderResponse 创建支付订单响应
|
||||
type CreatePaymentOrderResponse struct {
|
||||
OrderID string `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
CodeURL string `json:"code_url"` // 支付二维码URL(微信)
|
||||
PayURL string `json:"pay_url"` // 支付链接(支付宝)
|
||||
PaymentType string `json:"payment_type"`
|
||||
Amount string `json:"amount"`
|
||||
}
|
||||
|
||||
// CheckPaymentStatusResponse 检查支付状态响应
|
||||
type CheckPaymentStatusResponse struct {
|
||||
OrderID string `json:"order_id"` // 订单ID
|
||||
PaymentStatus string `json:"payment_status"` // 支付状态:pending, success, failed
|
||||
CanDownload bool `json:"can_download"` // 是否可以下载
|
||||
}
|
||||
|
||||
// UserOrderResponse 用户订单响应
|
||||
type UserOrderResponse struct {
|
||||
ID string `json:"id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductCode string `json:"product_code"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
PurchaseStatus string `json:"purchase_status"`
|
||||
Price string `json:"price"` // UI组件价格
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
PaymentTime *time.Time `json:"payment_time"`
|
||||
}
|
||||
@@ -14,6 +14,10 @@ type CreateProductCommand struct {
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||
@@ -35,6 +39,10 @@ type UpdateProductCommand struct {
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" binding:"omitempty,min=0" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||
|
||||
@@ -15,17 +15,20 @@ type PackageItemResponse struct {
|
||||
|
||||
// ProductInfoResponse 产品详情响应
|
||||
type ProductInfoResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
@@ -60,15 +63,15 @@ type ProductSearchResponse struct {
|
||||
|
||||
// ProductSimpleResponse 产品简单信息响应
|
||||
type ProductSimpleResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
OldID *string `json:"old_id,omitempty" comment:"旧产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
IsSubscribed *bool `json:"is_subscribed,omitempty" comment:"当前用户是否已订阅"`
|
||||
}
|
||||
|
||||
// ProductSimpleAdminResponse 管理员产品简单信息响应(包含成本价)
|
||||
@@ -101,6 +104,10 @@ type ProductAdminInfoResponse struct {
|
||||
IsVisible bool `json:"is_visible" comment:"是否可见"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// UI组件相关字段
|
||||
SellUIComponent bool `json:"sell_ui_component" comment:"是否出售UI组件"`
|
||||
UIComponentPrice float64 `json:"ui_component_price" comment:"UI组件销售价格(组合包使用)"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
|
||||
@@ -54,20 +54,22 @@ func NewProductApplicationService(
|
||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) (*responses.ProductAdminInfoResponse, error) {
|
||||
// 1. 构建产品实体
|
||||
product := &entities.Product{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: decimal.NewFromFloat(cmd.Price),
|
||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||
Remark: cmd.Remark,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
SEOTitle: cmd.SEOTitle,
|
||||
SEODescription: cmd.SEODescription,
|
||||
SEOKeywords: cmd.SEOKeywords,
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: decimal.NewFromFloat(cmd.Price),
|
||||
CostPrice: decimal.NewFromFloat(cmd.CostPrice),
|
||||
Remark: cmd.Remark,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
SellUIComponent: cmd.SellUIComponent,
|
||||
UIComponentPrice: decimal.NewFromFloat(cmd.UIComponentPrice),
|
||||
SEOTitle: cmd.SEOTitle,
|
||||
SEODescription: cmd.SEODescription,
|
||||
SEOKeywords: cmd.SEOKeywords,
|
||||
}
|
||||
|
||||
// 2. 创建产品
|
||||
@@ -101,6 +103,8 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
||||
existingProduct.IsEnabled = cmd.IsEnabled
|
||||
existingProduct.IsVisible = cmd.IsVisible
|
||||
existingProduct.IsPackage = cmd.IsPackage
|
||||
existingProduct.SellUIComponent = cmd.SellUIComponent
|
||||
existingProduct.UIComponentPrice = decimal.NewFromFloat(cmd.UIComponentPrice)
|
||||
existingProduct.SEOTitle = cmd.SEOTitle
|
||||
existingProduct.SEODescription = cmd.SEODescription
|
||||
existingProduct.SEOKeywords = cmd.SEOKeywords
|
||||
@@ -486,21 +490,23 @@ func (s *ProductApplicationServiceImpl) GetProductByIDForUser(ctx context.Contex
|
||||
// convertToProductInfoResponse 转换为产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||
response := &responses.ProductInfoResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加分类信息
|
||||
@@ -530,24 +536,26 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
||||
// convertToProductAdminInfoResponse 转换为管理员产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductAdminInfoResponse(product *entities.Product) *responses.ProductAdminInfoResponse {
|
||||
response := &responses.ProductAdminInfoResponse{
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
ID: product.ID,
|
||||
OldID: product.OldID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
CostPrice: product.CostPrice.InexactFloat64(),
|
||||
Remark: product.Remark,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible, // 管理员可以看到可见状态
|
||||
IsPackage: product.IsPackage,
|
||||
SellUIComponent: product.SellUIComponent,
|
||||
UIComponentPrice: product.UIComponentPrice.InexactFloat64(),
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加分类信息
|
||||
|
||||
@@ -182,7 +182,7 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFile(ctx contex
|
||||
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
createdComponent.FolderPath = &folderPath
|
||||
createdComponent.FileType = &fileType
|
||||
|
||||
@@ -255,7 +255,7 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFiles(ctx conte
|
||||
}
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
createdComponent.FolderPath = &folderPath
|
||||
|
||||
// 检查是否有ZIP文件
|
||||
@@ -363,7 +363,7 @@ func (s *UIComponentApplicationServiceImpl) CreateUIComponentWithFilesAndPaths(c
|
||||
}
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
createdComponent.FolderPath = &folderPath
|
||||
|
||||
// 检查是否有ZIP文件
|
||||
@@ -634,7 +634,7 @@ func (s *UIComponentApplicationServiceImpl) UploadAndExtractUIComponentFile(ctx
|
||||
fileType := strings.ToLower(filepath.Ext(file.Filename))
|
||||
|
||||
// 更新组件信息
|
||||
folderPath := "resources/Pure Component/src/ui"
|
||||
folderPath := "resources/Pure_Component/src/ui"
|
||||
component.FolderPath = &folderPath
|
||||
component.FileType = &fileType
|
||||
|
||||
|
||||
@@ -2772,18 +2772,19 @@ func (s *StatisticsApplicationServiceImpl) AdminGetTodayCertifiedEnterprises(ctx
|
||||
var enterprises []map[string]interface{}
|
||||
for _, cert := range completedCertifications {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取企业信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取用户基本信息(仅需要用户名)
|
||||
user, err := s.userRepo.GetByID(ctx, cert.UserID)
|
||||
// 使用预加载方法一次性获取用户和企业信息
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, cert.UserID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取用户信息失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取企业信息
|
||||
enterpriseInfo := user.EnterpriseInfo
|
||||
if enterpriseInfo == nil {
|
||||
s.logger.Warn("用户没有企业信息", zap.String("user_id", cert.UserID))
|
||||
continue
|
||||
}
|
||||
|
||||
enterprise := map[string]interface{}{
|
||||
"id": cert.ID,
|
||||
|
||||
Reference in New Issue
Block a user