package service import ( "errors" "fmt" "affiliate_dash/internal/model" "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" ) type WalletAdjustInput struct { MerchantID uint ActorUserID uint Amount int64 IdempotencyKey string Note string } func (s *FulfillmentService) AdjustWallet(in WalletAdjustInput) (*model.WalletAccount, error) { if in.Amount == 0 { return nil, errors.New("调整金额不能为零") } if in.IdempotencyKey == "" { return nil, errors.New("账务调整必须提供幂等键") } var out model.WalletAccount err := s.db.Transaction(func(tx *gorm.DB) error { var existing model.WalletLedgerEntry if err := tx.Where("merchant_id = ? AND idempotency_key = ?", in.MerchantID, in.IdempotencyKey).First(&existing).Error; err == nil { if err := tx.First(&out, existing.WalletAccountID).Error; err != nil { return err } return nil } else if !errors.Is(err, gorm.ErrRecordNotFound) { return err } var wallet model.WalletAccount if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("merchant_id = ?", in.MerchantID).First(&wallet).Error; err != nil { return err } newBalance := wallet.AvailableBalance + in.Amount if newBalance < 0 { return errors.New("调整后余额不能小于零") } if err := tx.Model(&wallet).Update("available_balance", newBalance).Error; err != nil { return err } entryType := model.WalletLedgerAdjust if in.Amount > 0 { entryType = model.WalletLedgerCredit } else { entryType = model.WalletLedgerDebit } idempotencyKey := in.IdempotencyKey if err := tx.Create(&model.WalletLedgerEntry{ MerchantID: in.MerchantID, WalletAccountID: wallet.ID, EntryNo: "WL" + uuid.NewString(), Type: entryType, Amount: in.Amount, BalanceAfter: newBalance, ReferenceType: "manual_adjustment", ReferenceNo: in.IdempotencyKey, IdempotencyKey: &idempotencyKey, Note: in.Note, }).Error; err != nil { return err } out = wallet out.AvailableBalance = newBalance return writeAudit(tx, &in.MerchantID, &in.ActorUserID, nil, "wallet.adjust", "wallet_account", fmt.Sprint(wallet.ID), map[string]int64{"amount": in.Amount}) }) if err != nil { return nil, err } return &out, nil } func (s *FulfillmentService) GetWallet(merchantID uint) (*model.WalletAccount, error) { var wallet model.WalletAccount if err := s.db.Where("merchant_id = ?", merchantID).First(&wallet).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("商户钱包不存在") } return nil, err } return &wallet, nil } func (s *FulfillmentService) ListWalletLedger(merchantID uint, page, size int, referenceNo, entryType string) ([]model.WalletLedgerEntry, int64, error) { page, size = normalizePage(page, size) tx := s.db.Model(&model.WalletLedgerEntry{}).Where("merchant_id = ?", merchantID) if referenceNo != "" { tx = tx.Where("reference_no LIKE ?", "%"+referenceNo+"%") } if entryType != "" { tx = tx.Where("type = ?", entryType) } var total int64 if err := tx.Count(&total).Error; err != nil { return nil, 0, err } var entries []model.WalletLedgerEntry err := tx.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&entries).Error return entries, total, err }