- 后端 internal/service 按职责拆分: fulfillment.go(1397→527)拆出 wallet/timeout/data/order/query/dashboard/shipnotify delivery.go(1124→801)拆出 upstream/link/state/helpers merchant.go(855→251)拆出 member/product/api_client/catalog/helpers - 前端 MerchantCenter.tsx(1327→606)拆出 merchantCenterTabs/merchantCenterUtils - docker-compose backend 透传 CORS_ALLOWED_ORIGINS - CORS 白名单实现(config/router/README/.env.example 配套) - 修复 gofmt 与文件尾部多余空行
111 lines
3.3 KiB
Go
111 lines
3.3 KiB
Go
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
|
|
}
|