106 lines
3.7 KiB
Go
106 lines
3.7 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/xerr"
|
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
|
)
|
|
|
|
// AdminCreditAsset 在一个事务内写入后台调账交易、分录和 outbox。
|
|
func (r *Repository) AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.AssetBalance{}, "", xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
|
}
|
|
ctx = contextWithCommandApp(ctx, command.AppCode)
|
|
command.AppCode = appcode.FromContext(ctx)
|
|
|
|
tx, err := r.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := adminCreditRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeManualCredit); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
balance, balanceErr := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, command.TargetUserID, command.AssetType)
|
|
return balance, txRow.TransactionID, balanceErr
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, command.AssetType, command.Amount > 0, nowMs)
|
|
if err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
if command.Amount < 0 && (command.Amount == math.MinInt64 || account.AvailableAmount < -command.Amount) {
|
|
return ledger.AssetBalance{}, "", xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
metadata := adminCreditMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: command.AssetType,
|
|
Amount: command.Amount,
|
|
OperatorUserID: command.OperatorUserID,
|
|
Reason: command.Reason,
|
|
EvidenceRef: command.EvidenceRef,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeManualCredit, requestHash, command.EvidenceRef, metadata, nowMs); err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
balance := ledger.AssetBalance{
|
|
AppCode: command.AppCode,
|
|
UserID: command.TargetUserID,
|
|
AssetType: command.AssetType,
|
|
AvailableAmount: account.AvailableAmount + command.Amount,
|
|
FrozenAmount: account.FrozenAmount,
|
|
Version: account.Version + 1,
|
|
UpdatedAtMs: nowMs,
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: command.AssetType,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balance.AvailableAmount,
|
|
FrozenAfter: balance.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, command.AssetType, command.Amount, 0, balance.AvailableAmount, balance.FrozenAmount, balance.Version, metadata, nowMs, bizTypeManualCredit),
|
|
}); err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.AssetBalance{}, "", err
|
|
}
|
|
|
|
return balance, transactionID, nil
|
|
}
|
|
|
|
func adminCreditRequestHash(command ledger.AdminCreditAssetCommand) string {
|
|
return stableHash(fmt.Sprintf("admin_credit|%s|%d|%s|%d|%d|%s|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
command.AssetType,
|
|
command.Amount,
|
|
command.OperatorUserID,
|
|
command.Reason,
|
|
command.EvidenceRef,
|
|
))
|
|
}
|