重构充值订单模块:用 tb_recharge_order + tb_payment 替换 tb_asset_recharge_record
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m32s

- 新增 RechargeOrder 和 Payment 模型及对应 Store
- 新增 ClientRechargeOrderHandler 提供充值订单列表/详情接口
- 修改 client_wallet.go 使用新表读写充值数据
- 修改 callback/payment.go 将 CRCH 订单路由到 rechargeOrderService
- 修改 client_order/service.go 的强充流程使用新表
- 修改 auto_purchase.go 从 tb_recharge_order 读取 linked_package_ids
- 修改 order/service.go 的 WalletPay 使用 tb_payment 记录
- 修改 wechat_config_store.go 从 tb_recharge_order 统计待支付充值数
- 移除 AssetRechargeStore 和 AssetRechargeRecord 的注册引用
- 修复文档生成器缺失 ClientRechargeOrder handler
- 状态枚举改为 0-based: Pending=0, Paid=1, Closed=2, Refunded=3
This commit is contained in:
2026-04-15 11:00:32 +08:00
parent d26010b29d
commit b972a776d9
33 changed files with 1589 additions and 1017 deletions

View File

@@ -19,14 +19,15 @@ import (
// AutoPurchasePayload 充值后自动购包任务载荷
type AutoPurchasePayload struct {
RechargeRecordID uint `json:"recharge_record_id"`
RechargeOrderID uint `json:"recharge_order_id"`
}
// AutoPurchaseHandler 充值后自动购包任务处理器
type AutoPurchaseHandler struct {
db *gorm.DB
orderStore *postgres.OrderStore
rechargeRecordStore *postgres.AssetRechargeStore
rechargeOrderStore *postgres.RechargeOrderStore
paymentStore *postgres.PaymentStore
walletStore *postgres.AssetWalletStore
walletTransactionStore *postgres.AssetWalletTransactionStore
packageUsageStore *postgres.PackageUsageStore
@@ -42,7 +43,8 @@ type AutoPurchaseHandler struct {
func NewAutoPurchaseHandler(
db *gorm.DB,
orderStore *postgres.OrderStore,
rechargeRecordStore *postgres.AssetRechargeStore,
rechargeOrderStore *postgres.RechargeOrderStore,
paymentStore *postgres.PaymentStore,
walletStore *postgres.AssetWalletStore,
walletTransactionStore *postgres.AssetWalletTransactionStore,
packageUsageStore *postgres.PackageUsageStore,
@@ -53,8 +55,11 @@ func NewAutoPurchaseHandler(
if orderStore == nil {
orderStore = postgres.NewOrderStore(db, redisClient)
}
if rechargeRecordStore == nil {
rechargeRecordStore = postgres.NewAssetRechargeStore(db, redisClient)
if rechargeOrderStore == nil {
rechargeOrderStore = postgres.NewRechargeOrderStore(db, redisClient)
}
if paymentStore == nil {
paymentStore = postgres.NewPaymentStore(db, redisClient)
}
if walletStore == nil {
walletStore = postgres.NewAssetWalletStore(db, redisClient)
@@ -69,7 +74,8 @@ func NewAutoPurchaseHandler(
return &AutoPurchaseHandler{
db: db,
orderStore: orderStore,
rechargeRecordStore: rechargeRecordStore,
rechargeOrderStore: rechargeOrderStore,
paymentStore: paymentStore,
walletStore: walletStore,
walletTransactionStore: walletTransactionStore,
packageUsageStore: packageUsageStore,
@@ -89,64 +95,64 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
h.logger.Error("解析自动购包任务载荷失败", zap.Error(err))
return asynq.SkipRetry
}
if payload.RechargeRecordID == 0 {
h.logger.Error("自动购包任务载荷无效", zap.Uint("recharge_record_id", payload.RechargeRecordID))
if payload.RechargeOrderID == 0 {
h.logger.Error("自动购包任务载荷无效", zap.Uint("recharge_order_id", payload.RechargeOrderID))
return asynq.SkipRetry
}
rechargeRecord, err := h.rechargeRecordStore.GetByID(ctx, payload.RechargeRecordID)
rechargeOrder, err := h.rechargeOrderStore.GetByID(ctx, payload.RechargeOrderID)
if err != nil {
if err == gorm.ErrRecordNotFound {
h.logger.Warn("充值记录不存在,跳过自动购包", zap.Uint("recharge_record_id", payload.RechargeRecordID))
h.logger.Warn("充值订单不存在,跳过自动购包", zap.Uint("recharge_order_id", payload.RechargeOrderID))
return asynq.SkipRetry
}
h.logger.Error("查询充值记录失败", zap.Uint("recharge_record_id", payload.RechargeRecordID), zap.Error(err))
h.logger.Error("查询充值订单失败", zap.Uint("recharge_order_id", payload.RechargeOrderID), zap.Error(err))
return err
}
if rechargeRecord.AutoPurchaseStatus == constants.AutoPurchaseStatusSuccess {
if rechargeOrder.AutoPurchaseStatus == constants.AutoPurchaseStatusSuccess {
return nil
}
if rechargeRecord.AutoPurchaseStatus == constants.AutoPurchaseStatusFailed {
if rechargeOrder.AutoPurchaseStatus == constants.AutoPurchaseStatusFailed {
return nil
}
packageIDs, err := parseLinkedPackageIDs(rechargeRecord.LinkedPackageIDs)
packageIDs, err := parseLinkedPackageIDs(rechargeOrder.LinkedPackageIDs)
if err != nil {
h.logger.Error("解析关联套餐ID失败", zap.Uint("recharge_record_id", rechargeRecord.ID), zap.Error(err))
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeRecord.ID)
h.logger.Error("解析关联套餐ID失败", zap.Uint("recharge_order_id", rechargeOrder.ID), zap.Error(err))
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeOrder.ID)
return asynq.SkipRetry
}
if len(packageIDs) == 0 {
h.logger.Error("关联套餐ID为空无法自动购包", zap.Uint("recharge_record_id", rechargeRecord.ID))
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeRecord.ID)
h.logger.Error("关联套餐ID为空无法自动购包", zap.Uint("recharge_order_id", rechargeOrder.ID))
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeOrder.ID)
return asynq.SkipRetry
}
packages, totalAmount, err := h.loadPackages(ctx, packageIDs)
if err != nil {
h.logger.Error("加载关联套餐失败", zap.Uint("recharge_record_id", rechargeRecord.ID), zap.Error(err))
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeRecord.ID)
h.logger.Error("加载关联套餐失败", zap.Uint("recharge_order_id", rechargeOrder.ID), zap.Error(err))
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeOrder.ID)
return err
}
// 获取资产系列ID用于差价佣金和一次性佣金计算
var seriesID *uint
if rechargeRecord.LinkedCarrierID != nil && *rechargeRecord.LinkedCarrierID > 0 {
if rechargeRecord.LinkedCarrierType == "card" || rechargeRecord.LinkedCarrierType == constants.AssetWalletResourceTypeIotCard {
if card, cardErr := h.iotCardStore.GetByID(ctx, *rechargeRecord.LinkedCarrierID); cardErr == nil {
if rechargeOrder.LinkedCarrierID != nil && *rechargeOrder.LinkedCarrierID > 0 {
if rechargeOrder.LinkedCarrierType == "card" || rechargeOrder.LinkedCarrierType == constants.AssetWalletResourceTypeIotCard {
if card, cardErr := h.iotCardStore.GetByID(ctx, *rechargeOrder.LinkedCarrierID); cardErr == nil {
seriesID = card.SeriesID
} else {
h.logger.Warn("自动购包获取卡系列ID失败",
zap.Uint("card_id", *rechargeRecord.LinkedCarrierID),
zap.Uint("card_id", *rechargeOrder.LinkedCarrierID),
zap.Error(cardErr))
}
} else if rechargeRecord.LinkedCarrierType == "device" || rechargeRecord.LinkedCarrierType == constants.AssetWalletResourceTypeDevice {
if device, deviceErr := h.deviceStore.GetByID(ctx, *rechargeRecord.LinkedCarrierID); deviceErr == nil {
} else if rechargeOrder.LinkedCarrierType == "device" || rechargeOrder.LinkedCarrierType == constants.AssetWalletResourceTypeDevice {
if device, deviceErr := h.deviceStore.GetByID(ctx, *rechargeOrder.LinkedCarrierID); deviceErr == nil {
seriesID = device.SeriesID
} else {
h.logger.Warn("自动购包获取设备系列ID失败",
zap.Uint("device_id", *rechargeRecord.LinkedCarrierID),
zap.Uint("device_id", *rechargeOrder.LinkedCarrierID),
zap.Error(deviceErr))
}
}
@@ -155,13 +161,13 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
// 获取卖家成本价,用于差价佣金链式计算的起点
// 平台直销ShopIDTag == 0时成本价为 0差价佣金计算会直接跳过
var sellerCostPrice int64
if rechargeRecord.ShopIDTag > 0 && len(packages) > 0 {
allocation, allocErr := h.shopPackageAllocationStore.GetByShopAndPackageForSystem(ctx, rechargeRecord.ShopIDTag, packages[0].ID)
if rechargeOrder.ShopIDTag > 0 && len(packages) > 0 {
allocation, allocErr := h.shopPackageAllocationStore.GetByShopAndPackageForSystem(ctx, rechargeOrder.ShopIDTag, packages[0].ID)
if allocErr == nil {
sellerCostPrice = allocation.CostPrice
} else {
h.logger.Warn("自动购包获取卖家成本价失败差价佣金将计算为0",
zap.Uint("shop_id", rechargeRecord.ShopIDTag),
zap.Uint("shop_id", rechargeOrder.ShopIDTag),
zap.Uint("package_id", packages[0].ID),
zap.Error(allocErr))
}
@@ -169,7 +175,7 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
var createdOrderID uint
if err := h.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
wallet, walletErr := h.walletStore.GetByID(ctx, rechargeRecord.AssetWalletID)
wallet, walletErr := h.walletStore.GetByID(ctx, rechargeOrder.AssetWalletID)
if walletErr != nil {
if walletErr == gorm.ErrRecordNotFound {
return errors.New("资产钱包不存在")
@@ -185,7 +191,7 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
}
now := time.Now()
order, orderItems, buildErr := h.buildOrderAndItems(rechargeRecord, packages, totalAmount, seriesID, sellerCostPrice, now)
order, orderItems, buildErr := h.buildOrderAndItems(rechargeOrder, packages, totalAmount, seriesID, sellerCostPrice, now)
if buildErr != nil {
return buildErr
}
@@ -202,12 +208,24 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
return err
}
payment := &model.Payment{
PaymentNo: order.OrderNo,
OrderID: order.ID,
OrderType: model.PaymentOrderTypePackage,
PaymentMethod: model.PaymentByWallet,
Amount: totalAmount,
Status: model.PaymentRecordStatusPaid,
}
if err = tx.Create(payment).Error; err != nil {
return err
}
refType := constants.ReferenceTypeOrder
walletTx := &model.AssetWalletTransaction{
AssetWalletID: wallet.ID,
ResourceType: wallet.ResourceType,
ResourceID: wallet.ResourceID,
UserID: rechargeRecord.UserID,
UserID: rechargeOrder.UserID,
TransactionType: constants.AssetTransactionTypeDeduct,
Amount: -totalAmount,
BalanceBefore: wallet.Balance,
@@ -215,7 +233,7 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
Status: constants.TransactionStatusSuccess,
ReferenceType: &refType,
ReferenceNo: &order.OrderNo,
Creator: rechargeRecord.UserID,
Creator: rechargeOrder.UserID,
ShopIDTag: wallet.ShopIDTag,
EnterpriseIDTag: wallet.EnterpriseIDTag,
}
@@ -227,8 +245,8 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
return err
}
if err = tx.Model(&model.AssetRechargeRecord{}).
Where("id = ?", rechargeRecord.ID).
if err = tx.Model(&model.RechargeOrder{}).
Where("id = ?", rechargeOrder.ID).
Update("auto_purchase_status", constants.AutoPurchaseStatusSuccess).Error; err != nil {
return err
}
@@ -236,10 +254,10 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
return nil
}); err != nil {
h.logger.Error("自动购包任务执行失败",
zap.Uint("recharge_record_id", rechargeRecord.ID),
zap.Uint("recharge_record_id", rechargeOrder.ID),
zap.Error(err),
)
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeRecord.ID)
h.markAutoPurchaseFailedIfFinalRetry(ctx, rechargeOrder.ID)
return err
}
@@ -263,13 +281,13 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
}
}
h.logger.Info("自动购包任务执行成功", zap.Uint("recharge_record_id", rechargeRecord.ID))
h.logger.Info("自动购包任务执行成功", zap.Uint("recharge_record_id", rechargeOrder.ID))
return nil
}
// NewAutoPurchaseTask 创建充值后自动购包任务
func NewAutoPurchaseTask(rechargeRecordID uint) (*asynq.Task, error) {
payloadBytes, err := sonic.Marshal(AutoPurchasePayload{RechargeRecordID: rechargeRecordID})
func NewAutoPurchaseTask(rechargeOrderID uint) (*asynq.Task, error) {
payloadBytes, err := sonic.Marshal(AutoPurchasePayload{RechargeOrderID: rechargeOrderID})
if err != nil {
return nil, err
}
@@ -281,7 +299,7 @@ func NewAutoPurchaseTask(rechargeRecordID uint) (*asynq.Task, error) {
), nil
}
func (h *AutoPurchaseHandler) markAutoPurchaseFailedIfFinalRetry(ctx context.Context, rechargeRecordID uint) {
func (h *AutoPurchaseHandler) markAutoPurchaseFailedIfFinalRetry(ctx context.Context, rechargeOrderID uint) {
retryCount, ok := asynq.GetRetryCount(ctx)
if !ok {
return
@@ -295,17 +313,17 @@ func (h *AutoPurchaseHandler) markAutoPurchaseFailedIfFinalRetry(ctx context.Con
}
if err := h.db.WithContext(ctx).
Model(&model.AssetRechargeRecord{}).
Where("id = ?", rechargeRecordID).
Model(&model.RechargeOrder{}).
Where("id = ?", rechargeOrderID).
Update("auto_purchase_status", constants.AutoPurchaseStatusFailed).Error; err != nil {
h.logger.Error("更新自动购包失败状态失败",
zap.Uint("recharge_record_id", rechargeRecordID),
zap.Uint("recharge_record_id", rechargeOrderID),
zap.Error(err),
)
return
}
h.logger.Warn("自动购包达到最大重试次数,已标记失败", zap.Uint("recharge_record_id", rechargeRecordID))
h.logger.Warn("自动购包达到最大重试次数,已标记失败", zap.Uint("recharge_record_id", rechargeOrderID))
}
func (h *AutoPurchaseHandler) loadPackages(ctx context.Context, packageIDs []uint) ([]*model.Package, int64, error) {
@@ -330,39 +348,39 @@ func (h *AutoPurchaseHandler) loadPackages(ctx context.Context, packageIDs []uin
}
func (h *AutoPurchaseHandler) buildOrderAndItems(
rechargeRecord *model.AssetRechargeRecord,
rechargeOrder *model.RechargeOrder,
packages []*model.Package,
totalAmount int64,
seriesID *uint,
sellerCostPrice int64,
now time.Time,
) (*model.Order, []*model.OrderItem, error) {
orderType, iotCardID, deviceID, err := parseLinkedCarrier(rechargeRecord.LinkedOrderType, rechargeRecord.LinkedCarrierType, rechargeRecord.LinkedCarrierID)
orderType, iotCardID, deviceID, err := parseLinkedCarrier(rechargeOrder.LinkedOrderType, rechargeOrder.LinkedCarrierType, rechargeOrder.LinkedCarrierID)
if err != nil {
return nil, nil, err
}
generation := rechargeRecord.Generation
generation := rechargeOrder.Generation
if generation <= 0 {
generation = 1
}
var sellerShopID *uint
if rechargeRecord.ShopIDTag > 0 {
shopID := rechargeRecord.ShopIDTag
if rechargeOrder.ShopIDTag > 0 {
shopID := rechargeOrder.ShopIDTag
sellerShopID = &shopID
}
paidAmount := totalAmount
order := &model.Order{
BaseModel: model.BaseModel{
Creator: rechargeRecord.UserID,
Updater: rechargeRecord.UserID,
Creator: rechargeOrder.UserID,
Updater: rechargeOrder.UserID,
},
OrderNo: h.orderStore.GenerateOrderNo(),
OrderType: orderType,
BuyerType: model.BuyerTypePersonal,
BuyerID: rechargeRecord.UserID,
BuyerID: rechargeOrder.UserID,
IotCardID: iotCardID,
DeviceID: deviceID,
TotalAmount: totalAmount,
@@ -383,8 +401,8 @@ func (h *AutoPurchaseHandler) buildOrderAndItems(
for _, pkg := range packages {
items = append(items, &model.OrderItem{
BaseModel: model.BaseModel{
Creator: rechargeRecord.UserID,
Updater: rechargeRecord.UserID,
Creator: rechargeOrder.UserID,
Updater: rechargeOrder.UserID,
},
PackageID: pkg.ID,
PackageName: pkg.PackageName,