add购买记录功能
This commit is contained in:
180
internal/domains/finance/entities/purchase_order.go
Normal file
180
internal/domains/finance/entities/purchase_order.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user