修复订单金额落库不对的问题
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m17s

This commit is contained in:
Break
2026-06-01 11:21:29 +08:00
parent 6c8352491c
commit 944526d9ef
16 changed files with 454 additions and 37 deletions

View File

@@ -136,7 +136,8 @@ func (h *AssetHandler) Packages(c *fiber.Ctx) error {
status = &parsedStatus
}
result, err := h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status)
callerAccountType := resolveCallerAccountType(c)
result, err := h.assetService.GetPackages(c.UserContext(), asset.AssetType, asset.AssetID, page, pageSize, status, callerAccountType)
if err != nil {
return err
}
@@ -152,7 +153,8 @@ func (h *AssetHandler) CurrentPackage(c *fiber.Ctx) error {
return err
}
result, err := h.assetService.GetCurrentPackage(c.UserContext(), asset.AssetType, asset.AssetID)
callerAccountType := resolveCallerAccountType(c)
result, err := h.assetService.GetCurrentPackage(c.UserContext(), asset.AssetType, asset.AssetID, callerAccountType)
if err != nil {
return err
}
@@ -428,3 +430,13 @@ func (h *AssetHandler) UpdateRealnameStatus(c *fiber.Ctx) error {
FirstRealnameAt: card.FirstRealnameAt,
})
}
// resolveCallerAccountType 从 middleware 上下文中提取调用方账号类型
// 平台账号SuperAdmin/Platform返回 "platform",其他返回 "agent"
func resolveCallerAccountType(c *fiber.Ctx) string {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform {
return constants.OwnerTypePlatform
}
return "agent"
}

View File

@@ -135,7 +135,8 @@ type AssetPackageResponse struct {
ExpiresAt *time.Time `json:"expires_at,omitempty" description:"到期时间(待生效套餐为空)"`
MasterUsageID *uint `json:"master_usage_id,omitempty" description:"主套餐ID加油包时有值"`
Priority int `json:"priority" description:"优先级"`
PaidAmount *int64 `json:"paid_amount,omitempty" description:"购买实付金额(分),线下支付或无订单分配时为空"`
PaidAmount *int64 `json:"paid_amount,omitempty" description:"购买成本价(分),仅平台账号可见,非平台账号不返回此字段"`
RetailAmount *int64 `json:"retail_amount,omitempty" description:"购买零售价(分),无订单关联时为空"`
CreatedAt time.Time `json:"created_at" description:"创建时间"`
}

View File

@@ -152,7 +152,8 @@ type OrderItem struct {
PackageName string `gorm:"column:package_name;type:varchar(100);not null;comment:套餐名称(快照)" json:"package_name"`
PackageType *string `gorm:"column:package_type;type:varchar(20);comment:套餐类型快照 formal-正式套餐 addon-加油包" json:"package_type"`
Quantity int `gorm:"column:quantity;type:int;default:1;not null;comment:数量" json:"quantity"`
UnitPrice int64 `gorm:"column:unit_price;type:bigint;not null;comment:单价(分)" json:"unit_price"`
UnitPrice int64 `gorm:"column:unit_price;type:bigint;not null;comment:零售单价快照(分,客户面值)" json:"unit_price"`
CostPrice int64 `gorm:"column:cost_price;type:bigint;not null;default:0;comment:成本单价快照(分,代购/钱包支付场景为买家成本平台自营或赠送时为0" json:"cost_price"`
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:小计金额(分)" json:"amount"`
PackagePriceConfigStatus int `gorm:"column:package_price_config_status;type:int;default:0;not null;comment:套餐价格配置状态快照 0-未配置 1-赠送0价 2-已配置非0" json:"package_price_config_status"`
PackageIsGift bool `gorm:"column:package_is_gift;type:boolean;default:false;not null;comment:套餐是否赠送快照 true-赠送 false-普通可售" json:"package_is_gift"`

View File

@@ -84,7 +84,8 @@ type PackageUsage struct {
HasIndependentExpiry bool `gorm:"column:has_independent_expiry;type:boolean;default:false;comment:加油包是否有独立有效期(true-有独立到期时间 false-跟随主套餐)" json:"has_independent_expiry"`
PendingRealnameActivation bool `gorm:"column:pending_realname_activation;type:boolean;default:false;comment:是否等待实名激活(true-待实名后激活 false-已激活或不需实名)" json:"pending_realname_activation"`
PackageName string `gorm:"column:package_name;type:varchar(255);not null;default:'';comment:套餐名称快照(从Package复制,用于历史记录展示)" json:"package_name"`
PaidAmount *int64 `gorm:"column:paid_amount;type:bigint;comment:购买实付金额快照从订单复制无订单或线下支付时为null" json:"paid_amount,omitempty"`
PaidAmount *int64 `gorm:"column:paid_amount;type:bigint;comment:购买实付金额快照(分,从订单 actual_paid_amount 复制无订单或线下支付时为null" json:"paid_amount,omitempty"`
RetailAmount *int64 `gorm:"column:retail_amount;type:bigint;comment:购买零售价快照(分,从订单 total_amount 复制无订单关联时为null" json:"retail_amount,omitempty"`
PackagePriceConfigStatus int `gorm:"column:package_price_config_status;type:int;default:0;not null;comment:套餐价格配置状态快照 0-未配置 1-赠送0价 2-已配置非0" json:"package_price_config_status"`
PackageIsGift bool `gorm:"column:package_is_gift;type:boolean;default:false;not null;comment:套餐是否赠送快照 true-赠送 false-普通可售" json:"package_is_gift"`
DataResetCycle string `gorm:"column:data_reset_cycle;type:varchar(20);comment:流量重置周期(从Package复制,用于历史记录)" json:"data_reset_cycle"`

View File

@@ -696,8 +696,9 @@ func parseGatewayTime(raw gateway.FlexString) *time.Time {
}
// GetPackages 获取资产的所有套餐列表(支持分页和状态筛选)
// callerAccountType: 调用方账号类型,"platform" 时返回成本价paid_amount其他类型不返回
// page 默认 1pageSize 默认 50pageSize 最大 100
func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int, status *int) (*dto.AssetPackagesResult, error) {
func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int, status *int, callerAccountType string) (*dto.AssetPackagesResult, error) {
// 分页参数边界处理
if page < 1 {
page = 1
@@ -757,6 +758,10 @@ func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, pa
pkgType = pkg.PackageType
expiryBase = pkg.ExpiryBase
}
var paidAmount *int64
if callerAccountType == constants.OwnerTypePlatform {
paidAmount = u.PaidAmount
}
item := &dto.AssetPackageResponse{
PackageUsageID: u.ID,
PackageID: u.PackageID,
@@ -780,7 +785,8 @@ func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, pa
ExpiresAt: u.ExpiresAt,
MasterUsageID: u.MasterUsageID,
Priority: u.Priority,
PaidAmount: u.PaidAmount,
PaidAmount: paidAmount,
RetailAmount: u.RetailAmount,
CreatedAt: u.CreatedAt,
}
all = append(all, item)
@@ -811,7 +817,8 @@ func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, pa
}
// GetCurrentPackage 获取资产当前生效的主套餐
func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id uint) (*dto.AssetPackageResponse, error) {
// callerAccountType: 调用方账号类型,"platform" 时返回成本价paid_amount其他类型不返回
func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id uint, callerAccountType string) (*dto.AssetPackageResponse, error) {
carrierType := assetType
if assetType == "card" {
carrierType = "iot_card"
@@ -839,6 +846,10 @@ func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id ui
pkgType = pkg.PackageType
expiryBase = pkg.ExpiryBase
}
var paidAmount *int64
if callerAccountType == constants.OwnerTypePlatform {
paidAmount = usage.PaidAmount
}
return &dto.AssetPackageResponse{
PackageUsageID: usage.ID,
PackageID: usage.PackageID,
@@ -862,7 +873,8 @@ func (s *Service) GetCurrentPackage(ctx context.Context, assetType string, id ui
ExpiresAt: usage.ExpiresAt,
MasterUsageID: usage.MasterUsageID,
Priority: usage.Priority,
PaidAmount: usage.PaidAmount,
PaidAmount: paidAmount,
RetailAmount: usage.RetailAmount,
CreatedAt: usage.CreatedAt,
}, nil
}

View File

@@ -242,6 +242,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
purchaseRole := ""
var sellerShopID *uint = resourceShopID
var sellerCostPrice int64
var itemCostPriceMap map[uint]int64 // 各套餐成本单价映射,用于 OrderItem.CostPrice 字段
// 根据支付方式分别处理
if req.PaymentMethod == model.PaymentMethodOffline {
@@ -249,7 +250,6 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
if containsGift {
orderBuyerType = model.BuyerTypePersonal
orderBuyerID = 0
totalAmount = 0
paymentMethod = model.PaymentMethodOffline
paymentStatus = model.PaymentStatusPaid
paidAt = &now
@@ -286,8 +286,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
}
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = purchaseBuyerID
itemUnitPriceMap = buyerCostPriceMap
totalAmount = buyerTotalCost
itemCostPriceMap = buyerCostPriceMap
paymentMethod = model.PaymentMethodOffline
paymentStatus = model.PaymentStatusPaid
paidAt = purchasePaidAt
@@ -298,7 +297,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
operatorID = nil
operatorType = constants.OwnerTypePlatform
purchaseRole = model.PurchaseRolePurchasedByPlatform
actualPaidAmount = nil
actualPaidAmount = &buyerTotalCost
}
}
@@ -320,8 +319,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = operatorShopID
itemUnitPriceMap = operatorCostPriceMap
totalAmount = operatorTotalCost
itemCostPriceMap = operatorCostPriceMap
paymentMethod = model.PaymentMethodWallet
paymentStatus = model.PaymentStatusPaid
paidAt = &now
@@ -336,13 +334,13 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
} else {
// ==== 子场景 2.2:代理代购(给下级购买)====
// 获取买家成本价
buyerCostPriceMap, buyerTotalCost, err := s.buildCostPriceMap(ctx, validationResult.Packages, *resourceShopID)
// 获取买家成本价(买家的价格映射用于 CostPrice 快照,总价不再使用)
buyerCostPriceMap, _, err := s.buildCostPriceMap(ctx, validationResult.Packages, *resourceShopID)
if err != nil {
return nil, err
}
// 获取操作者成本价
// 获取操作者成本价(操作方实际扣款金额和佣金基准)
_, operatorTotalCost, err := s.buildCostPriceMap(ctx, validationResult.Packages, operatorShopID)
if err != nil {
return nil, err
@@ -350,8 +348,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = *resourceShopID
itemUnitPriceMap = buyerCostPriceMap
totalAmount = buyerTotalCost
itemCostPriceMap = buyerCostPriceMap
paymentMethod = model.PaymentMethodWallet
paymentStatus = model.PaymentStatusPaid
paidAt = &now
@@ -375,8 +372,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = targetShopID
itemUnitPriceMap = targetCostPriceMap
totalAmount = targetTotalCost
itemCostPriceMap = targetCostPriceMap
paymentMethod = model.PaymentMethodWallet
paymentStatus = model.PaymentStatusPaid
paidAt = &now
@@ -465,7 +461,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
order.BuyerPhone, order.BuyerNickname = s.fetchBuyerSnapshot(ctx, orderBuyerID)
}
items := s.buildOrderItems(userID, validationResult.Packages, itemUnitPriceMap)
items := s.buildOrderItems(userID, validationResult.Packages, itemUnitPriceMap, itemCostPriceMap)
idempotencyKey := buildOrderIdempotencyKey(buyerType, buyerID, orderType, carrierType, carrierID, req.PackageIDs)
@@ -606,7 +602,8 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
purchaseRole := ""
var sellerShopID *uint = resourceShopID
var sellerCostPrice int64
var expiresAt *time.Time // 待支付订单设置过期时间,立即支付的订单为 nil
var expiresAt *time.Time // 待支付订单设置过期时间,立即支付的订单为 nil
var itemCostPriceMap map[uint]int64 // 各套餐成本单价映射,用于 OrderItem.CostPrice 字段
// 场景判断offline平台代购、wallet代理钱包支付、其他待支付
if req.PaymentMethod == model.PaymentMethodOffline {
@@ -617,8 +614,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
}
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = purchaseBuyerID
itemUnitPriceMap = buyerCostPriceMap
totalAmount = buyerTotalCost
itemCostPriceMap = buyerCostPriceMap
paymentMethod = model.PaymentMethodOffline
paymentStatus = model.PaymentStatusPaid
paidAt = purchasePaidAt
@@ -629,7 +625,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
operatorID = nil
operatorType = constants.OwnerTypePlatform
purchaseRole = model.PurchaseRolePurchasedByPlatform
actualPaidAmount = nil
actualPaidAmount = &buyerTotalCost
} else if req.PaymentMethod == model.PaymentMethodWallet {
// ==== 场景 2代理钱包支付wallet====
@@ -653,8 +649,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = operatorShopID
itemUnitPriceMap = operatorCostPriceMap
totalAmount = operatorTotalCost
itemCostPriceMap = operatorCostPriceMap
paymentMethod = model.PaymentMethodWallet
paymentStatus = model.PaymentStatusPaid
paidAt = &now
@@ -669,13 +664,13 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
} else {
// ==== 子场景 2.2:代理代购(给下级购买)====
// 获取买家成本价
buyerCostPriceMap, buyerTotalCost, err := s.buildCostPriceMap(ctx, validationResult.Packages, *resourceShopID)
// 获取买家成本价(价格映射用于 CostPrice 快照,总价不再使用)
buyerCostPriceMap, _, err := s.buildCostPriceMap(ctx, validationResult.Packages, *resourceShopID)
if err != nil {
return nil, err
}
// 获取操作者成本价
// 获取操作者成本价(操作方实际扣款金额和佣金基准)
_, operatorTotalCost, err := s.buildCostPriceMap(ctx, validationResult.Packages, operatorShopID)
if err != nil {
return nil, err
@@ -683,8 +678,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = *resourceShopID
itemUnitPriceMap = buyerCostPriceMap
totalAmount = buyerTotalCost
itemCostPriceMap = buyerCostPriceMap
paymentMethod = model.PaymentMethodWallet
paymentStatus = model.PaymentStatusPaid
paidAt = &now
@@ -750,7 +744,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
PaymentConfigID: h5PaymentConfigID,
}
items := s.buildOrderItems(userID, validationResult.Packages, itemUnitPriceMap)
items := s.buildOrderItems(userID, validationResult.Packages, itemUnitPriceMap, itemCostPriceMap)
idempotencyKey := buildOrderIdempotencyKey(buyerType, buyerID, req.OrderType, carrierType, carrierID, req.PackageIDs)
@@ -824,7 +818,9 @@ func (s *Service) resolvePurchaseOnBehalfInfo(ctx context.Context, result *purch
return *resourceShopID, buyerCostPriceMap, buyerTotalCost, &now, nil
}
func (s *Service) buildOrderItems(operatorID uint, packages []*model.Package, unitPriceMap map[uint]int64) []*model.OrderItem {
// buildOrderItems 构建订单明细列表
// retailPriceMap: 零售单价映射UnitPricecostPriceMap: 成本单价映射CostPrice无成本价时传 nil
func (s *Service) buildOrderItems(operatorID uint, packages []*model.Package, retailPriceMap map[uint]int64, costPriceMap map[uint]int64) []*model.OrderItem {
items := make([]*model.OrderItem, 0, len(packages))
for _, pkg := range packages {
if pkg == nil {
@@ -832,10 +828,15 @@ func (s *Service) buildOrderItems(operatorID uint, packages []*model.Package, un
}
unitPrice := packageprice.PackageEffectiveRetailPrice(pkg)
if price, ok := unitPriceMap[pkg.ID]; ok {
if price, ok := retailPriceMap[pkg.ID]; ok {
unitPrice = price
}
var costPrice int64
if costPriceMap != nil {
costPrice = costPriceMap[pkg.ID]
}
item := &model.OrderItem{
BaseModel: model.BaseModel{
Creator: operatorID,
@@ -846,6 +847,7 @@ func (s *Service) buildOrderItems(operatorID uint, packages []*model.Package, un
PackageType: &pkg.PackageType,
Quantity: 1,
UnitPrice: unitPrice,
CostPrice: costPrice,
Amount: unitPrice,
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
@@ -2295,6 +2297,7 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
// 创建套餐使用记录
virtualTotalMBSnapshot, displayGainRatioSnapshot, enableVirtualDataSnapshot := model.BuildPackageUsageSnapshotValues(pkg)
retailAmount := order.TotalAmount
usage := &model.PackageUsage{
BaseModel: model.BaseModel{
Creator: order.Creator,
@@ -2314,6 +2317,7 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
DataResetCycle: pkg.DataResetCycle,
PendingRealnameActivation: pendingRealnameActivation,
PaidAmount: order.ActualPaidAmount,
RetailAmount: &retailAmount,
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
}
@@ -2380,6 +2384,7 @@ func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *
}
virtualTotalMBSnapshot, displayGainRatioSnapshot, enableVirtualDataSnapshot := model.BuildPackageUsageSnapshotValues(pkg)
addonRetailAmount := order.TotalAmount
usage := &model.PackageUsage{
BaseModel: model.BaseModel{
Creator: order.Creator,
@@ -2401,6 +2406,7 @@ func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *
ExpiresAt: &expiresAt,
DataResetCycle: pkg.DataResetCycle,
PaidAmount: order.ActualPaidAmount,
RetailAmount: &addonRetailAmount,
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
}

View File

@@ -532,6 +532,7 @@ func (h *AutoPurchaseHandler) activateMainPackage(
// ExpiryBase 仅影响后台囤货路径,此处无需判断
virtualTotalMBSnapshot, displayGainRatioSnapshot, enableVirtualDataSnapshot := model.BuildPackageUsageSnapshotValues(pkg)
retailAmount := order.TotalAmount
usage := &model.PackageUsage{
BaseModel: model.BaseModel{
Creator: order.Creator,
@@ -552,6 +553,7 @@ func (h *AutoPurchaseHandler) activateMainPackage(
PendingRealnameActivation: pendingRealnameActivation,
Generation: order.Generation,
PaidAmount: order.ActualPaidAmount,
RetailAmount: &retailAmount,
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
}
@@ -606,6 +608,7 @@ func (h *AutoPurchaseHandler) activateAddonPackage(
expiresAt := mainPackage.ExpiresAt
virtualTotalMBSnapshot, displayGainRatioSnapshot, enableVirtualDataSnapshot := model.BuildPackageUsageSnapshotValues(pkg)
addonRetailAmount := order.TotalAmount
usage := &model.PackageUsage{
BaseModel: model.BaseModel{
Creator: order.Creator,
@@ -628,6 +631,7 @@ func (h *AutoPurchaseHandler) activateAddonPackage(
DataResetCycle: pkg.DataResetCycle,
Generation: order.Generation,
PaidAmount: order.ActualPaidAmount,
RetailAmount: &addonRetailAmount,
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
}