重构充值订单模块:用 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

@@ -1,250 +1,5 @@
package postgres
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
// AssetRechargeStore 资产充值记录数据访问层
type AssetRechargeStore struct {
db *gorm.DB
redis *redis.Client
}
// NewAssetRechargeStore 创建资产充值记录 Store
func NewAssetRechargeStore(db *gorm.DB, redis *redis.Client) *AssetRechargeStore {
return &AssetRechargeStore{
db: db,
redis: redis,
}
}
// Create 创建充值记录
func (s *AssetRechargeStore) Create(ctx context.Context, record *model.AssetRechargeRecord) error {
return s.db.WithContext(ctx).Create(record).Error
}
// CreateWithTx 创建充值记录(带事务)
func (s *AssetRechargeStore) CreateWithTx(ctx context.Context, tx *gorm.DB, record *model.AssetRechargeRecord) error {
return tx.WithContext(ctx).Create(record).Error
}
// GetByRechargeNo 根据充值订单号查询
func (s *AssetRechargeStore) GetByRechargeNo(ctx context.Context, rechargeNo string) (*model.AssetRechargeRecord, error) {
var record model.AssetRechargeRecord
err := s.db.WithContext(ctx).
Where("recharge_no = ?", rechargeNo).
First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// GetByID 根据 ID 查询
func (s *AssetRechargeStore) GetByID(ctx context.Context, id uint) (*model.AssetRechargeRecord, error) {
var record model.AssetRechargeRecord
if err := s.db.WithContext(ctx).First(&record, id).Error; err != nil {
return nil, err
}
return &record, nil
}
// UpdateStatus 更新充值状态
func (s *AssetRechargeStore) UpdateStatus(ctx context.Context, id uint, status int) error {
return s.db.WithContext(ctx).
Model(&model.AssetRechargeRecord{}).
Where("id = ?", id).
Update("status", status).Error
}
// UpdateStatusWithTx 更新充值状态(带事务)
func (s *AssetRechargeStore) UpdateStatusWithTx(ctx context.Context, tx *gorm.DB, id uint, status int) error {
return tx.WithContext(ctx).
Model(&model.AssetRechargeRecord{}).
Where("id = ?", id).
Update("status", status).Error
}
// Update 更新充值记录
func (s *AssetRechargeStore) Update(ctx context.Context, record *model.AssetRechargeRecord) error {
return s.db.WithContext(ctx).Save(record).Error
}
// UpdateWithTx 更新充值记录(带事务)
func (s *AssetRechargeStore) UpdateWithTx(ctx context.Context, tx *gorm.DB, record *model.AssetRechargeRecord) error {
return tx.WithContext(ctx).Save(record).Error
}
// ListByResourceID 按资源查询充值记录(支持分页)
func (s *AssetRechargeStore) ListByResourceID(ctx context.Context, resourceType string, resourceID uint, offset, limit int) ([]*model.AssetRechargeRecord, error) {
var records []*model.AssetRechargeRecord
err := s.db.WithContext(ctx).
Where("resource_type = ? AND resource_id = ?", resourceType, resourceID).
Order("created_at DESC").
Offset(offset).
Limit(limit).
Find(&records).Error
if err != nil {
return nil, err
}
return records, nil
}
// ListByUserID 按用户查询充值记录(支持分页)
func (s *AssetRechargeStore) ListByUserID(ctx context.Context, userID uint, offset, limit int) ([]*model.AssetRechargeRecord, error) {
var records []*model.AssetRechargeRecord
err := s.db.WithContext(ctx).
Where("user_id = ?", userID).
Order("created_at DESC").
Offset(offset).
Limit(limit).
Find(&records).Error
if err != nil {
return nil, err
}
return records, nil
}
// ListByStatus 按状态查询充值记录(支持分页)
func (s *AssetRechargeStore) ListByStatus(ctx context.Context, status int, offset, limit int) ([]*model.AssetRechargeRecord, error) {
var records []*model.AssetRechargeRecord
err := s.db.WithContext(ctx).
Where("status = ?", status).
Order("created_at DESC").
Offset(offset).
Limit(limit).
Find(&records).Error
if err != nil {
return nil, err
}
return records, nil
}
// ListAssetRechargeParams 充值记录列表查询参数
type ListAssetRechargeParams struct {
Page int
PageSize int
UserID *uint
AssetWalletID *uint
ResourceType *string
ResourceID *uint
Status *int
StartTime interface{}
EndTime interface{}
}
// List 查询充值记录列表(支持分页和筛选)
func (s *AssetRechargeStore) List(ctx context.Context, params *ListAssetRechargeParams) ([]*model.AssetRechargeRecord, int64, error) {
var records []*model.AssetRechargeRecord
var total int64
query := s.db.WithContext(ctx).Model(&model.AssetRechargeRecord{})
if params.UserID != nil {
query = query.Where("user_id = ?", *params.UserID)
}
if params.AssetWalletID != nil {
query = query.Where("asset_wallet_id = ?", *params.AssetWalletID)
}
if params.ResourceType != nil {
query = query.Where("resource_type = ?", *params.ResourceType)
}
if params.ResourceID != nil {
query = query.Where("resource_id = ?", *params.ResourceID)
}
if params.Status != nil {
query = query.Where("status = ?", *params.Status)
}
if params.StartTime != nil {
query = query.Where("created_at >= ?", params.StartTime)
}
if params.EndTime != nil {
query = query.Where("created_at <= ?", params.EndTime)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
page := params.Page
if page < 1 {
page = 1
}
pageSize := params.PageSize
if pageSize < 1 {
pageSize = 20
}
offset := (page - 1) * pageSize
if err := query.Order("id DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
// UpdatePaymentInfo 更新支付信息
func (s *AssetRechargeStore) UpdatePaymentInfo(ctx context.Context, id uint, paymentMethod *string, paymentTransactionID *string) error {
return s.UpdatePaymentInfoWithDB(ctx, s.db, id, paymentMethod, paymentTransactionID)
}
// UpdatePaymentInfoWithDB 更新支付信息(支持传入事务 tx
func (s *AssetRechargeStore) UpdatePaymentInfoWithDB(ctx context.Context, db *gorm.DB, id uint, paymentMethod *string, paymentTransactionID *string) error {
updates := map[string]interface{}{}
if paymentMethod != nil {
updates["payment_method"] = paymentMethod
}
if paymentTransactionID != nil {
updates["payment_transaction_id"] = paymentTransactionID
}
if len(updates) == 0 {
return nil
}
result := db.WithContext(ctx).Model(&model.AssetRechargeRecord{}).Where("id = ?", id).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
// UpdateStatusWithOptimisticLock 更新充值状态(支持乐观锁)
func (s *AssetRechargeStore) UpdateStatusWithOptimisticLock(ctx context.Context, id uint, oldStatus *int, newStatus int, paidAt interface{}, completedAt interface{}) error {
return s.UpdateStatusWithOptimisticLockDB(ctx, s.db, id, oldStatus, newStatus, paidAt, completedAt)
}
// UpdateStatusWithOptimisticLockDB 更新充值状态(支持传入事务 tx
func (s *AssetRechargeStore) UpdateStatusWithOptimisticLockDB(ctx context.Context, db *gorm.DB, id uint, oldStatus *int, newStatus int, paidAt interface{}, completedAt interface{}) error {
updates := map[string]interface{}{
"status": newStatus,
}
if paidAt != nil {
updates["paid_at"] = paidAt
}
if completedAt != nil {
updates["completed_at"] = completedAt
}
query := db.WithContext(ctx).Model(&model.AssetRechargeRecord{}).Where("id = ?", id)
if oldStatus != nil {
query = query.Where("status = ?", *oldStatus)
}
result := query.Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
// 已废弃 - 请使用 RechargeOrderStore 和 PaymentStore
type AssetRechargeStore struct{}

View File

@@ -0,0 +1,152 @@
package postgres
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type PaymentStore struct {
db *gorm.DB
redis *redis.Client
}
func NewPaymentStore(db *gorm.DB, redis *redis.Client) *PaymentStore {
return &PaymentStore{
db: db,
redis: redis,
}
}
func (s *PaymentStore) Create(ctx context.Context, payment *model.Payment) error {
return s.db.WithContext(ctx).Create(payment).Error
}
func (s *PaymentStore) CreateWithTx(ctx context.Context, tx *gorm.DB, payment *model.Payment) error {
return tx.WithContext(ctx).Create(payment).Error
}
func (s *PaymentStore) GetByPaymentNo(ctx context.Context, paymentNo string) (*model.Payment, error) {
var payment model.Payment
err := s.db.WithContext(ctx).
Where("payment_no = ?", paymentNo).
First(&payment).Error
if err != nil {
return nil, err
}
return &payment, nil
}
func (s *PaymentStore) GetByID(ctx context.Context, id uint) (*model.Payment, error) {
var payment model.Payment
err := s.db.WithContext(ctx).First(&payment, id).Error
if err != nil {
return nil, err
}
return &payment, nil
}
func (s *PaymentStore) UpdateStatus(ctx context.Context, id uint, status int) error {
return s.db.WithContext(ctx).
Model(&model.Payment{}).
Where("id = ?", id).
Update("status", status).Error
}
func (s *PaymentStore) UpdateStatusWithTx(ctx context.Context, tx *gorm.DB, id uint, status int) error {
return tx.WithContext(ctx).
Model(&model.Payment{}).
Where("id = ?", id).
Update("status", status).Error
}
func (s *PaymentStore) UpdateStatusWithOptimisticLock(ctx context.Context, id uint, oldStatus *int, newStatus int, paidAt interface{}) error {
return s.UpdateStatusWithOptimisticLockDB(ctx, s.db, id, oldStatus, newStatus, paidAt)
}
func (s *PaymentStore) UpdateStatusWithOptimisticLockDB(ctx context.Context, db *gorm.DB, id uint, oldStatus *int, newStatus int, paidAt interface{}) error {
updates := map[string]interface{}{
"status": newStatus,
}
if paidAt != nil {
updates["paid_at"] = paidAt
}
query := db.WithContext(ctx).Model(&model.Payment{}).Where("id = ?", id)
if oldStatus != nil {
query = query.Where("status = ?", *oldStatus)
}
result := query.Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
func (s *PaymentStore) UpdatePaymentInfo(ctx context.Context, id uint, thirdPartyTradeNo string, paymentConfigID *uint) error {
return s.UpdatePaymentInfoWithDB(ctx, s.db, id, thirdPartyTradeNo, paymentConfigID)
}
func (s *PaymentStore) UpdatePaymentInfoWithDB(ctx context.Context, db *gorm.DB, id uint, thirdPartyTradeNo string, paymentConfigID *uint) error {
updates := map[string]interface{}{}
if thirdPartyTradeNo != "" {
updates["third_party_trade_no"] = thirdPartyTradeNo
}
if paymentConfigID != nil {
updates["payment_config_id"] = *paymentConfigID
}
if len(updates) == 0 {
return nil
}
result := db.WithContext(ctx).Model(&model.Payment{}).Where("id = ?", id).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
func (s *PaymentStore) ListByOrderID(ctx context.Context, orderID uint, orderType string) ([]*model.Payment, error) {
var payments []*model.Payment
err := s.db.WithContext(ctx).
Where("order_id = ? AND order_type = ?", orderID, orderType).
Order("created_at DESC").
Find(&payments).Error
if err != nil {
return nil, err
}
return payments, nil
}
func (s *PaymentStore) ListByOrderIDAndStatus(ctx context.Context, orderID uint, orderType string, status int) (*model.Payment, error) {
var payment model.Payment
err := s.db.WithContext(ctx).
Where("order_id = ? AND order_type = ? AND status = ?", orderID, orderType, status).
First(&payment).Error
if err != nil {
return nil, err
}
return &payment, nil
}
func (s *PaymentStore) GetByOrderIDAndStatus(ctx context.Context, orderID uint, status int) (*model.Payment, error) {
var payment model.Payment
err := s.db.WithContext(ctx).
Where("order_id = ? AND status = ?", orderID, status).
First(&payment).Error
if err != nil {
return nil, err
}
return &payment, nil
}

View File

@@ -0,0 +1,188 @@
package postgres
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type RechargeOrderStore struct {
db *gorm.DB
redis *redis.Client
}
func NewRechargeOrderStore(db *gorm.DB, redis *redis.Client) *RechargeOrderStore {
return &RechargeOrderStore{
db: db,
redis: redis,
}
}
func (s *RechargeOrderStore) Create(ctx context.Context, order *model.RechargeOrder) error {
return s.db.WithContext(ctx).Create(order).Error
}
func (s *RechargeOrderStore) CreateWithTx(ctx context.Context, tx *gorm.DB, order *model.RechargeOrder) error {
return tx.WithContext(ctx).Create(order).Error
}
func (s *RechargeOrderStore) GetByRechargeOrderNo(ctx context.Context, orderNo string) (*model.RechargeOrder, error) {
var order model.RechargeOrder
err := s.db.WithContext(ctx).
Where("recharge_order_no = ?", orderNo).
First(&order).Error
if err != nil {
return nil, err
}
return &order, nil
}
func (s *RechargeOrderStore) GetByID(ctx context.Context, id uint) (*model.RechargeOrder, error) {
var order model.RechargeOrder
err := s.db.WithContext(ctx).First(&order, id).Error
if err != nil {
return nil, err
}
return &order, nil
}
func (s *RechargeOrderStore) UpdateStatus(ctx context.Context, id uint, status int) error {
return s.db.WithContext(ctx).
Model(&model.RechargeOrder{}).
Where("id = ?", id).
Update("status", status).Error
}
func (s *RechargeOrderStore) UpdateStatusWithTx(ctx context.Context, tx *gorm.DB, id uint, status int) error {
return tx.WithContext(ctx).
Model(&model.RechargeOrder{}).
Where("id = ?", id).
Update("status", status).Error
}
func (s *RechargeOrderStore) UpdateStatusWithOptimisticLock(ctx context.Context, id uint, oldStatus *int, newStatus int, paidAt interface{}) error {
return s.UpdateStatusWithOptimisticLockDB(ctx, s.db, id, oldStatus, newStatus, paidAt)
}
func (s *RechargeOrderStore) UpdateStatusWithOptimisticLockDB(ctx context.Context, db *gorm.DB, id uint, oldStatus *int, newStatus int, paidAt interface{}) error {
updates := map[string]interface{}{
"status": newStatus,
}
if paidAt != nil {
updates["paid_at"] = paidAt
}
query := db.WithContext(ctx).Model(&model.RechargeOrder{}).Where("id = ?", id)
if oldStatus != nil {
query = query.Where("status = ?", *oldStatus)
}
result := query.Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
func (s *RechargeOrderStore) Update(ctx context.Context, order *model.RechargeOrder) error {
return s.db.WithContext(ctx).Save(order).Error
}
func (s *RechargeOrderStore) ListByCard(ctx context.Context, iotCardID uint, generation int, offset, limit int) ([]*model.RechargeOrder, error) {
var orders []*model.RechargeOrder
query := s.db.WithContext(ctx).
Where("iot_card_id = ? AND generation = ?", iotCardID, generation)
if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&orders).Error; err != nil {
return nil, err
}
return orders, nil
}
func (s *RechargeOrderStore) ListByDevice(ctx context.Context, deviceID uint, generation int, offset, limit int) ([]*model.RechargeOrder, error) {
var orders []*model.RechargeOrder
query := s.db.WithContext(ctx).
Where("device_id = ? AND generation = ?", deviceID, generation)
if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&orders).Error; err != nil {
return nil, err
}
return orders, nil
}
func (s *RechargeOrderStore) ListByUserID(ctx context.Context, userID uint, offset, limit int) ([]*model.RechargeOrder, error) {
var orders []*model.RechargeOrder
err := s.db.WithContext(ctx).
Where("user_id = ?", userID).
Order("created_at DESC").
Offset(offset).
Limit(limit).
Find(&orders).Error
if err != nil {
return nil, err
}
return orders, nil
}
type ListRechargeOrderParams struct {
Page int
PageSize int
UserID *uint
IotCardID *uint
DeviceID *uint
Status *int
StartTime interface{}
EndTime interface{}
}
func (s *RechargeOrderStore) List(ctx context.Context, params *ListRechargeOrderParams) ([]*model.RechargeOrder, int64, error) {
var orders []*model.RechargeOrder
var total int64
query := s.db.WithContext(ctx).Model(&model.RechargeOrder{})
if params.UserID != nil {
query = query.Where("user_id = ?", *params.UserID)
}
if params.IotCardID != nil {
query = query.Where("iot_card_id = ?", *params.IotCardID)
}
if params.DeviceID != nil {
query = query.Where("device_id = ?", *params.DeviceID)
}
if params.Status != nil {
query = query.Where("status = ?", *params.Status)
}
if params.StartTime != nil {
query = query.Where("created_at >= ?", params.StartTime)
}
if params.EndTime != nil {
query = query.Where("created_at <= ?", params.EndTime)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
page := params.Page
if page < 1 {
page = 1
}
pageSize := params.PageSize
if pageSize < 1 {
pageSize = 20
}
offset := (page - 1) * pageSize
if err := query.Order("id DESC").Offset(offset).Limit(pageSize).Find(&orders).Error; err != nil {
return nil, 0, err
}
return orders, total, nil
}

View File

@@ -145,7 +145,7 @@ func (s *WechatConfigStore) CountPendingOrdersByConfigID(ctx context.Context, co
func (s *WechatConfigStore) CountPendingRechargesByConfigID(ctx context.Context, configID uint) (int64, error) {
var count int64
err := s.db.WithContext(ctx).
Table("tb_asset_recharge_record").
Table("tb_recharge_order").
Where("payment_config_id = ? AND status = ? AND deleted_at IS NULL", configID, 1).
Count(&count).Error
return count, err