Files
junhong_cmp_fiber/internal/service/recharge/service.go
huang b972a776d9
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m32s
重构充值订单模块:用 tb_recharge_order + tb_payment 替换 tb_asset_recharge_record
- 新增 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
2026-04-15 11:00:32 +08:00

199 lines
7.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package recharge
import (
"context"
"fmt"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/payment"
"go.uber.org/zap"
"gorm.io/gorm"
)
// ForceRechargeRequirement 强充要求信息
type ForceRechargeRequirement struct {
NeedForceRecharge bool `json:"need_force_recharge"` // 是否需要强充
ForceRechargeAmount int64 `json:"force_recharge_amount"` // 强充金额(分)
TriggerType string `json:"trigger_type"` // 触发类型: single_recharge/accumulated_recharge
MinAmount int64 `json:"min_amount"` // 最小充值金额
MaxAmount int64 `json:"max_amount"` // 最大充值金额
CurrentAccumulated int64 `json:"current_accumulated"` // 当前累计充值
Threshold int64 `json:"threshold"` // 佣金触发阈值
Message string `json:"message"` // 提示信息
}
// WechatConfigServiceInterface 支付配置服务接口
type WechatConfigServiceInterface interface {
GetActiveConfig(ctx context.Context) (*model.WechatConfig, error)
}
// Service 充值服务
// 负责资产钱包IoT卡/设备)的充值订单创建、预检、支付回调处理等业务逻辑
type Service struct {
db *gorm.DB
assetWalletStore *postgres.AssetWalletStore
assetWalletTransactionStore *postgres.AssetWalletTransactionStore
iotCardStore *postgres.IotCardStore
deviceStore *postgres.DeviceStore
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
packageSeriesStore *postgres.PackageSeriesStore
commissionRecordStore *postgres.CommissionRecordStore
wechatConfigService WechatConfigServiceInterface
paymentLoader payment.PaymentConfigLoader
logger *zap.Logger
}
// New 创建充值服务实例
func New(
db *gorm.DB,
assetWalletStore *postgres.AssetWalletStore,
assetWalletTransactionStore *postgres.AssetWalletTransactionStore,
iotCardStore *postgres.IotCardStore,
deviceStore *postgres.DeviceStore,
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
packageSeriesStore *postgres.PackageSeriesStore,
commissionRecordStore *postgres.CommissionRecordStore,
wechatConfigService WechatConfigServiceInterface,
paymentLoader payment.PaymentConfigLoader,
logger *zap.Logger,
) *Service {
return &Service{
db: db,
assetWalletStore: assetWalletStore,
assetWalletTransactionStore: assetWalletTransactionStore,
iotCardStore: iotCardStore,
deviceStore: deviceStore,
shopSeriesAllocationStore: shopSeriesAllocationStore,
packageSeriesStore: packageSeriesStore,
commissionRecordStore: commissionRecordStore,
wechatConfigService: wechatConfigService,
paymentLoader: paymentLoader,
logger: logger,
}
}
// GetRechargeCheck 充值预检
// 返回强充要求、金额限制等信息
func (s *Service) GetRechargeCheck(ctx context.Context, resourceType string, resourceID uint) (*ForceRechargeRequirement, error) {
// 验证资源类型
if resourceType != "iot_card" && resourceType != "device" {
return nil, errors.New(errors.CodeInvalidParam, "无效的资源类型")
}
// 验证资源是否存在
if resourceType == "iot_card" {
if _, err := s.iotCardStore.GetByID(ctx, resourceID); err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeIotCardNotFound, "IoT卡不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
}
} else {
if _, err := s.deviceStore.GetByID(ctx, resourceID); err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "设备不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
}
}
return s.checkForceRechargeRequirement(ctx, resourceType, resourceID)
}
// checkForceRechargeRequirement 检查强充要求
// 根据资源类型和ID检查是否需要强制充值指定金额
func (s *Service) checkForceRechargeRequirement(ctx context.Context, resourceType string, resourceID uint) (*ForceRechargeRequirement, error) {
result := &ForceRechargeRequirement{
NeedForceRecharge: false,
MinAmount: constants.AssetRechargeMinAmount,
MaxAmount: constants.AssetRechargeMaxAmount,
Message: "无强充要求,可自由充值",
}
var seriesID *uint
var shopID *uint
var accumulatedRecharge int64
var firstCommissionPaid bool
if resourceType == "iot_card" {
card, err := s.iotCardStore.GetByID(ctx, resourceID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeIotCardNotFound, "IoT卡不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询IoT卡失败")
}
seriesID = card.SeriesID
shopID = card.ShopID
if seriesID != nil {
accumulatedRecharge = card.GetAccumulatedRechargeBySeries(*seriesID)
firstCommissionPaid = card.IsFirstRechargeTriggeredBySeries(*seriesID)
}
} else if resourceType == "device" {
device, err := s.deviceStore.GetByID(ctx, resourceID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "设备不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询设备失败")
}
seriesID = device.SeriesID
shopID = device.ShopID
if seriesID != nil {
accumulatedRecharge = device.GetAccumulatedRechargeBySeries(*seriesID)
firstCommissionPaid = device.IsFirstRechargeTriggeredBySeries(*seriesID)
}
}
result.CurrentAccumulated = accumulatedRecharge
if seriesID == nil || shopID == nil {
return result, nil
}
series, err := s.packageSeriesStore.GetByID(ctx, *seriesID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return result, nil
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐系列失败")
}
config, err := series.GetOneTimeCommissionConfig()
if err != nil || config == nil || !config.Enable {
return result, nil
}
result.Threshold = config.Threshold
result.TriggerType = config.TriggerType
if firstCommissionPaid {
result.Message = "一次性佣金已发放,无强充要求"
return result, nil
}
if config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge {
result.NeedForceRecharge = true
result.ForceRechargeAmount = config.Threshold
result.Message = fmt.Sprintf("首次充值必须充值%d分", config.Threshold)
} else if config.EnableForceRecharge {
result.NeedForceRecharge = true
if config.ForceAmount > 0 {
result.ForceRechargeAmount = config.ForceAmount
} else {
result.ForceRechargeAmount = config.Threshold
}
result.Message = fmt.Sprintf("每次充值必须充值%d分", result.ForceRechargeAmount)
} else {
result.Message = "累计充值模式,可自由充值"
}
return result, nil
}
// updateAccumulatedRechargeInTx 更新累计充值(事务内使用)
// 同时更新旧的 accumulated_recharge 字段和新的 accumulated_recharge_by_series JSON 字段