add购买记录功能

This commit is contained in:
2025-12-22 18:32:34 +08:00
parent 65a61d0336
commit 7f8554fa12
314 changed files with 4029 additions and 83496 deletions

View File

@@ -0,0 +1,180 @@
package entities
import (
"fmt"
"math/rand"
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// PurchaseOrderStatus 购买订单状态枚举(通用)
type PurchaseOrderStatus string
const (
PurchaseOrderStatusCreated PurchaseOrderStatus = "created" // 已创建
PurchaseOrderStatusPaid PurchaseOrderStatus = "paid" // 已支付
PurchaseOrderStatusFailed PurchaseOrderStatus = "failed" // 支付失败
PurchaseOrderStatusCancelled PurchaseOrderStatus = "cancelled" // 已取消
PurchaseOrderStatusRefunded PurchaseOrderStatus = "refunded" // 已退款
PurchaseOrderStatusClosed PurchaseOrderStatus = "closed" // 已关闭
)
// PurchaseOrder 购买订单实体(统一表 ty_purchase_orders兼容多支付渠道
type PurchaseOrder struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"购买订单唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"购买用户ID"`
OrderNo string `gorm:"type:varchar(64);not null;uniqueIndex" json:"order_no" comment:"商户订单号"`
TradeNo *string `gorm:"type:varchar(64);uniqueIndex" json:"trade_no,omitempty" comment:"第三方支付交易号"`
// 产品信息
ProductID string `gorm:"type:varchar(36);not null;index" json:"product_id" comment:"产品ID"`
ProductCode string `gorm:"type:varchar(50);not null" json:"product_code" comment:"产品编号"`
ProductName string `gorm:"type:varchar(200);not null" json:"product_name" comment:"产品名称"`
Category string `gorm:"type:varchar(50)" json:"category,omitempty" comment:"产品分类"`
// 订单信息
Subject string `gorm:"type:varchar(200);not null" json:"subject" comment:"订单标题"`
Amount decimal.Decimal `gorm:"type:decimal(20,8);not null" json:"amount" comment:"订单金额"`
PayAmount *decimal.Decimal `gorm:"type:decimal(20,8)" json:"pay_amount,omitempty" comment:"实际支付金额"`
Status PurchaseOrderStatus `gorm:"type:varchar(20);not null;default:'created';index" json:"status" comment:"订单状态"`
Platform string `gorm:"type:varchar(20);not null" json:"platform" comment:"下单平台app/h5/pc/wx_h5/wx_mini等"`
PayChannel string `gorm:"type:varchar(20);default:'alipay';index" json:"pay_channel" comment:"支付渠道alipay/wechat"`
PaymentType string `gorm:"type:varchar(20);not null" json:"payment_type" comment:"支付类型alipay, wechat, free"`
// 支付渠道返回信息
BuyerID string `gorm:"type:varchar(64)" json:"buyer_id,omitempty" comment:"买家ID支付渠道方"`
SellerID string `gorm:"type:varchar(64)" json:"seller_id,omitempty" comment:"卖家ID支付渠道方"`
ReceiptAmount decimal.Decimal `gorm:"type:decimal(20,8)" json:"receipt_amount,omitempty" comment:"实收金额"`
// 回调信息
NotifyTime *time.Time `gorm:"index" json:"notify_time,omitempty" comment:"异步通知时间"`
ReturnTime *time.Time `gorm:"index" json:"return_time,omitempty" comment:"同步返回时间"`
PayTime *time.Time `gorm:"index" json:"pay_time,omitempty" comment:"支付完成时间"`
// 文件信息
FilePath *string `gorm:"type:varchar(500)" json:"file_path,omitempty" comment:"产品文件路径"`
FileSize *int64 `gorm:"type:bigint" json:"file_size,omitempty" comment:"文件大小(字节)"`
// 备注信息
Remark string `gorm:"type:varchar(500)" json:"remark,omitempty" comment:"备注信息"`
// 错误信息
ErrorCode string `gorm:"type:varchar(64)" json:"error_code,omitempty" comment:"错误码"`
ErrorMessage string `gorm:"type:text" json:"error_message,omitempty" comment:"错误信息"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
}
// TableName 指定数据库表名
func (PurchaseOrder) TableName() string {
return "ty_purchase_orders"
}
// BeforeCreate GORM钩子创建前自动生成UUID和订单号
func (p *PurchaseOrder) BeforeCreate(tx *gorm.DB) error {
if p.ID == "" {
p.ID = uuid.New().String()
}
if p.OrderNo == "" {
p.OrderNo = generatePurchaseOrderNo()
}
return nil
}
// generatePurchaseOrderNo 生成购买订单号
func generatePurchaseOrderNo() string {
// 使用时间戳+随机数生成唯一订单号例如PO202312200001
timestamp := time.Now().Format("20060102")
random := fmt.Sprintf("%04d", rand.Intn(9999))
return fmt.Sprintf("PO%s%s", timestamp, random)
}
// IsCreated 检查是否为已创建状态
func (p *PurchaseOrder) IsCreated() bool {
return p.Status == PurchaseOrderStatusCreated
}
// IsPaid 检查是否为已支付状态
func (p *PurchaseOrder) IsPaid() bool {
return p.Status == PurchaseOrderStatusPaid
}
// IsFailed 检查是否为支付失败状态
func (p *PurchaseOrder) IsFailed() bool {
return p.Status == PurchaseOrderStatusFailed
}
// IsCancelled 检查是否为已取消状态
func (p *PurchaseOrder) IsCancelled() bool {
return p.Status == PurchaseOrderStatusCancelled
}
// IsRefunded 检查是否为已退款状态
func (p *PurchaseOrder) IsRefunded() bool {
return p.Status == PurchaseOrderStatusRefunded
}
// IsClosed 检查是否为已关闭状态
func (p *PurchaseOrder) IsClosed() bool {
return p.Status == PurchaseOrderStatusClosed
}
// MarkPaid 标记为已支付
func (p *PurchaseOrder) MarkPaid(tradeNo, buyerID, sellerID string, payAmount, receiptAmount decimal.Decimal) {
p.Status = PurchaseOrderStatusPaid
p.TradeNo = &tradeNo
p.BuyerID = buyerID
p.SellerID = sellerID
p.PayAmount = &payAmount
p.ReceiptAmount = receiptAmount
now := time.Now()
p.PayTime = &now
p.NotifyTime = &now
}
// MarkFailed 标记为支付失败
func (p *PurchaseOrder) MarkFailed(errorCode, errorMessage string) {
p.Status = PurchaseOrderStatusFailed
p.ErrorCode = errorCode
p.ErrorMessage = errorMessage
}
// MarkCancelled 标记为已取消
func (p *PurchaseOrder) MarkCancelled() {
p.Status = PurchaseOrderStatusCancelled
}
// MarkRefunded 标记为已退款
func (p *PurchaseOrder) MarkRefunded() {
p.Status = PurchaseOrderStatusRefunded
}
// MarkClosed 标记为已关闭
func (p *PurchaseOrder) MarkClosed() {
p.Status = PurchaseOrderStatusClosed
}
// NewPurchaseOrder 通用工厂方法 - 创建购买订单(支持多支付渠道)
func NewPurchaseOrder(userID, productID, productCode, productName, subject string, amount decimal.Decimal, platform, payChannel, paymentType string) *PurchaseOrder {
return &PurchaseOrder{
ID: uuid.New().String(),
UserID: userID,
OrderNo: generatePurchaseOrderNo(),
ProductID: productID,
ProductCode: productCode,
ProductName: productName,
Subject: subject,
Amount: amount,
Status: PurchaseOrderStatusCreated,
Platform: platform,
PayChannel: payChannel,
PaymentType: paymentType,
}
}

View File

@@ -0,0 +1,63 @@
package repositories
import (
"context"
"time"
finance_entities "tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/shared/interfaces"
)
// PurchaseOrderRepository 购买订单仓储接口
type PurchaseOrderRepository interface {
// 创建订单
Create(ctx context.Context, order *finance_entities.PurchaseOrder) (*finance_entities.PurchaseOrder, error)
// 更新订单
Update(ctx context.Context, order *finance_entities.PurchaseOrder) error
// 根据ID获取订单
GetByID(ctx context.Context, id string) (*finance_entities.PurchaseOrder, error)
// 根据订单号获取订单
GetByOrderNo(ctx context.Context, orderNo string) (*finance_entities.PurchaseOrder, error)
// 根据用户ID获取订单列表
GetByUserID(ctx context.Context, userID string, limit, offset int) ([]*finance_entities.PurchaseOrder, int64, error)
// 根据产品ID和用户ID获取订单
GetByUserIDAndProductID(ctx context.Context, userID, productID string) (*finance_entities.PurchaseOrder, error)
// 根据支付类型和第三方交易号获取订单
GetByPaymentTypeAndTransactionID(ctx context.Context, paymentType, transactionID string) (*finance_entities.PurchaseOrder, error)
// 根据交易号获取订单
GetByTradeNo(ctx context.Context, tradeNo string) (*finance_entities.PurchaseOrder, error)
// 更新支付状态
UpdatePaymentStatus(ctx context.Context, orderID string, status finance_entities.PurchaseOrderStatus, tradeNo *string, payAmount, receiptAmount *string, paymentTime *time.Time) error
// 获取用户已购买的产品编号列表
GetUserPurchasedProductCodes(ctx context.Context, userID string) ([]string, error)
// 检查用户是否已购买指定产品
HasUserPurchased(ctx context.Context, userID string, productCode string) (bool, error)
// 获取即将过期的订单(用于清理)
GetExpiringOrders(ctx context.Context, before time.Time, limit int) ([]*finance_entities.PurchaseOrder, error)
// 获取已过期订单(用于清理)
GetExpiredOrders(ctx context.Context, limit int) ([]*finance_entities.PurchaseOrder, error)
// 获取用户已支付的产品ID列表
GetUserPaidProductIDs(ctx context.Context, userID string) ([]string, error)
// 根据状态获取订单列表
GetByStatus(ctx context.Context, status finance_entities.PurchaseOrderStatus, limit, offset int) ([]*finance_entities.PurchaseOrder, int64, error)
// 根据筛选条件获取订单列表
GetByFilters(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*finance_entities.PurchaseOrder, error)
// 根据筛选条件统计订单数量
CountByFilters(ctx context.Context, filters map[string]interface{}) (int64, error)
}

View File

@@ -4,29 +4,31 @@ import (
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
// ComponentReportDownload 组件报告下载记录
type ComponentReportDownload struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"下载记录ID"`
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
ProductCode string `gorm:"type:varchar(50);not null;index" comment:"产品编号"`
SubProductIDs string `gorm:"type:text" comment:"产品ID列表JSON数组组合包使用"`
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表JSON数组"`
DownloadPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"实际支付价格"`
OriginalPrice decimal.Decimal `gorm:"type:decimal(10,2);not null" comment:"原始总价"`
DiscountAmount decimal.Decimal `gorm:"type:decimal(10,2);default:0" comment:"减免金额"`
PaymentOrderID *string `gorm:"type:varchar(64);index" comment:"支付订单号(关联充值记录)"`
PaymentType *string `gorm:"type:varchar(20)" comment:"支付类型alipay, wechat"`
PaymentStatus string `gorm:"type:varchar(20);default:'pending';index" comment:"支付状态pending, success, failed"`
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径用于二次下载"`
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证)"`
DownloadCount int `gorm:"default:0" comment:"下载次数"`
LastDownloadAt *time.Time `comment:"最后下载时间"`
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期支付成功后30天"`
ID string `gorm:"primaryKey;type:varchar(36)" comment:"下载记录ID"`
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
ProductCode string `gorm:"type:varchar(50);not null;index" comment:"产品编号"`
ProductName string `gorm:"type:varchar(200);not null" comment:"产品名称"`
// 直接关联购买订单
OrderID *string `gorm:"type:varchar(36);index" comment:"关联的购买订单ID"`
OrderNumber *string `gorm:"type:varchar(64);index" comment:"关联的购买订单号"`
// 组合包相关字段(从购买记录复制)
SubProductIDs string `gorm:"type:text" comment:"子产品ID列表JSON数组组合包使用"`
SubProductCodes string `gorm:"type:text" comment:"子产品编号列表JSON数组"`
// 下载相关信息
FilePath *string `gorm:"type:varchar(500)" comment:"生成的ZIP文件路径用于二次下载"`
FileHash *string `gorm:"type:varchar(64)" comment:"文件哈希值(用于缓存验证"`
DownloadCount int `gorm:"default:0" comment:"下载次数"`
LastDownloadAt *time.Time `comment:"最后下载时间"`
ExpiresAt *time.Time `gorm:"index" comment:"下载有效期从创建日起30天"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
@@ -46,11 +48,6 @@ func (c *ComponentReportDownload) BeforeCreate(tx *gorm.DB) error {
return nil
}
// IsPaid 检查是否已支付
func (c *ComponentReportDownload) IsPaid() bool {
return c.PaymentStatus == "success"
}
// IsExpired 检查是否已过期
func (c *ComponentReportDownload) IsExpired() bool {
if c.ExpiresAt == nil {
@@ -61,5 +58,6 @@ func (c *ComponentReportDownload) IsExpired() bool {
// CanDownload 检查是否可以下载
func (c *ComponentReportDownload) CanDownload() bool {
return c.IsPaid() && !c.IsExpired()
// 下载记录存在即表示用户有下载权限,只需检查是否过期
return !c.IsExpired()
}

View File

@@ -9,7 +9,7 @@ import (
// ComponentReportRepository 组件报告仓储接口
type ComponentReportRepository interface {
// 创建下载记录
CreateDownload(ctx context.Context, download *entities.ComponentReportDownload) (*entities.ComponentReportDownload, error)
Create(ctx context.Context, download *entities.ComponentReportDownload) error
// 更新下载记录
UpdateDownload(ctx context.Context, download *entities.ComponentReportDownload) error
@@ -20,6 +20,15 @@ type ComponentReportRepository interface {
// 获取用户的下载记录列表
GetUserDownloads(ctx context.Context, userID string, productID *string) ([]*entities.ComponentReportDownload, error)
// 获取用户有效的下载记录
GetActiveDownload(ctx context.Context, userID, productID string) (*entities.ComponentReportDownload, error)
// 更新下载记录文件路径
UpdateFilePath(ctx context.Context, downloadID, filePath string) error
// 增加下载次数
IncrementDownloadCount(ctx context.Context, downloadID string) error
// 检查用户是否已下载过指定产品编号的组件
HasUserDownloaded(ctx context.Context, userID string, productCode string) (bool, error)

View File

@@ -115,7 +115,7 @@ func (s *UserAggregateServiceImpl) CreateUser(ctx context.Context, phone, passwo
func (s *UserAggregateServiceImpl) LoadUser(ctx context.Context, userID string) (*entities.User, error) {
s.logger.Debug("加载用户聚合根", zap.String("user_id", userID))
user, err := s.userRepo.GetByID(ctx, userID)
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, userID)
if err != nil {
return nil, fmt.Errorf("用户不存在: %w", err)
}