761 lines
29 KiB
Go
761 lines
29 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/xerr"
|
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
|
)
|
|
|
|
// DebitCPBreakupFee 在钱包账本中扣除解除关系费用;关系状态不在 wallet 中修改,避免 wallet 反向拥有用户关系事实。
|
|
func (r *Repository) DebitCPBreakupFee(ctx context.Context, command ledger.CPBreakupFeeCommand) (ledger.CPBreakupFeeReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.CPBreakupFeeReceipt{}, 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.CPBreakupFeeReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := cpBreakupFeeRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeCPBreakupFee); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
balance, balanceErr := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, command.UserID, ledger.AssetCoin)
|
|
if balanceErr != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, balanceErr
|
|
}
|
|
return ledger.CPBreakupFeeReceipt{
|
|
TransactionID: txRow.TransactionID,
|
|
CoinSpent: command.Amount,
|
|
CoinBalanceAfter: balance.AvailableAmount,
|
|
Balance: balance,
|
|
}, nil
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, false, nowMs)
|
|
if err != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
if account.AvailableAmount < command.Amount {
|
|
return ledger.CPBreakupFeeReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount - command.Amount
|
|
metadata := cpBreakupFeeMetadata{
|
|
AppCode: command.AppCode,
|
|
UserID: command.UserID,
|
|
RelationshipID: command.RelationshipID,
|
|
RelationType: command.RelationType,
|
|
Amount: command.Amount,
|
|
CoinBalanceAfter: balanceAfter,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeCPBreakupFee, requestHash, command.RelationshipID, metadata, nowMs); err != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, -command.Amount, 0, nowMs); err != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
balance := ledger.AssetBalance{
|
|
AppCode: command.AppCode,
|
|
UserID: command.UserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableAmount: balanceAfter,
|
|
FrozenAmount: account.FrozenAmount,
|
|
Version: account.Version + 1,
|
|
UpdatedAtMs: nowMs,
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.UserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: -command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balance.AvailableAmount,
|
|
FrozenAfter: balance.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.UserID, ledger.AssetCoin, -command.Amount, 0, balance.AvailableAmount, balance.FrozenAmount, balance.Version, metadata, nowMs, bizTypeCPBreakupFee),
|
|
}); err != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.CPBreakupFeeReceipt{}, err
|
|
}
|
|
return ledger.CPBreakupFeeReceipt{
|
|
TransactionID: transactionID,
|
|
CoinSpent: command.Amount,
|
|
CoinBalanceAfter: balance.AvailableAmount,
|
|
Balance: balance,
|
|
}, nil
|
|
}
|
|
|
|
// DebitWheelDraw 在钱包账本中扣除转盘抽奖金币;中奖和返奖由 activity-service 后续处理,钱包只保证付费事实可审计、可幂等。
|
|
func (r *Repository) DebitWheelDraw(ctx context.Context, command ledger.WheelDrawDebitCommand) (ledger.WheelDrawDebitReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.WheelDrawDebitReceipt{}, 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.WheelDrawDebitReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := wheelDrawDebitRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeWheelDrawDebit); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
balance, balanceErr := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, command.UserID, ledger.AssetCoin)
|
|
if balanceErr != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, balanceErr
|
|
}
|
|
return ledger.WheelDrawDebitReceipt{
|
|
TransactionID: txRow.TransactionID,
|
|
CoinSpent: command.Amount,
|
|
CoinBalanceAfter: balance.AvailableAmount,
|
|
Balance: balance,
|
|
}, nil
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, false, nowMs)
|
|
if err != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
if account.AvailableAmount < command.Amount {
|
|
return ledger.WheelDrawDebitReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount - command.Amount
|
|
metadata := wheelDrawDebitMetadata{
|
|
AppCode: command.AppCode,
|
|
UserID: command.UserID,
|
|
WheelID: command.WheelID,
|
|
DrawCount: command.DrawCount,
|
|
Amount: command.Amount,
|
|
CoinBalanceAfter: balanceAfter,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeWheelDrawDebit, requestHash, command.WheelID, metadata, nowMs); err != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, -command.Amount, 0, nowMs); err != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
balance := ledger.AssetBalance{
|
|
AppCode: command.AppCode,
|
|
UserID: command.UserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableAmount: balanceAfter,
|
|
FrozenAmount: account.FrozenAmount,
|
|
Version: account.Version + 1,
|
|
UpdatedAtMs: nowMs,
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.UserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: -command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balance.AvailableAmount,
|
|
FrozenAfter: balance.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.UserID, ledger.AssetCoin, -command.Amount, 0, balance.AvailableAmount, balance.FrozenAmount, balance.Version, metadata, nowMs, bizTypeWheelDrawDebit),
|
|
}); err != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.WheelDrawDebitReceipt{}, err
|
|
}
|
|
return ledger.WheelDrawDebitReceipt{
|
|
TransactionID: transactionID,
|
|
CoinSpent: command.Amount,
|
|
CoinBalanceAfter: balance.AvailableAmount,
|
|
Balance: balance,
|
|
}, nil
|
|
}
|
|
|
|
// CreditTaskReward 在一个事务内完成任务奖励资产入账、交易分录和 outbox 事件。
|
|
func (r *Repository) CreditTaskReward(ctx context.Context, command ledger.TaskRewardCommand) (ledger.TaskRewardReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.TaskRewardReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
|
}
|
|
ctx = contextWithCommandApp(ctx, command.AppCode)
|
|
command.AppCode = appcode.FromContext(ctx)
|
|
command.AssetType = strings.ToUpper(strings.TrimSpace(command.AssetType))
|
|
if command.AssetType == "" {
|
|
command.AssetType = ledger.AssetCoin
|
|
}
|
|
|
|
tx, err := r.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := taskRewardRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeTaskReward); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
return r.receiptForTaskRewardTransaction(ctx, tx, txRow.TransactionID)
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, command.AssetType, true, nowMs)
|
|
if err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount + command.Amount
|
|
metadata := taskRewardMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: command.AssetType,
|
|
Amount: command.Amount,
|
|
TaskType: command.TaskType,
|
|
TaskID: command.TaskID,
|
|
CycleKey: command.CycleKey,
|
|
Reason: command.Reason,
|
|
BalanceAfter: balanceAfter,
|
|
GrantedAtMS: nowMs,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeTaskReward, requestHash, fmt.Sprintf("%s:%s", command.TaskID, command.CycleKey), metadata, nowMs); err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: command.AssetType,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balanceAfter,
|
|
FrozenAfter: account.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, command.AssetType, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
|
taskRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
|
}); err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.TaskRewardReceipt{}, err
|
|
}
|
|
|
|
return receiptFromTaskRewardMetadata(transactionID, metadata), nil
|
|
}
|
|
|
|
// CreditLuckyGiftReward 在同一事务里完成幸运礼物中奖 COIN 入账、交易分录和钱包 outbox。
|
|
func (r *Repository) CreditLuckyGiftReward(ctx context.Context, command ledger.LuckyGiftRewardCommand) (ledger.LuckyGiftRewardReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, 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.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := luckyGiftRewardRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeLuckyGiftReward); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
return r.receiptForLuckyGiftRewardTransaction(ctx, tx, txRow.TransactionID)
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs)
|
|
if err != nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount + command.Amount
|
|
metadata := luckyGiftRewardMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
Amount: command.Amount,
|
|
DrawID: command.DrawID,
|
|
RoomID: command.RoomID,
|
|
VisibleRegionID: command.VisibleRegionID,
|
|
CountryID: command.CountryID,
|
|
GiftID: command.GiftID,
|
|
PoolID: command.PoolID,
|
|
Reason: command.Reason,
|
|
BalanceAfter: balanceAfter,
|
|
GrantedAtMS: nowMs,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeLuckyGiftReward, requestHash, command.DrawID, metadata, nowMs); err != nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balanceAfter,
|
|
FrozenAfter: account.FrozenAmount,
|
|
RoomID: command.RoomID,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
|
luckyGiftRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
|
}); err != nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.LuckyGiftRewardReceipt{}, err
|
|
}
|
|
|
|
return receiptFromLuckyGiftRewardMetadata(transactionID, metadata), nil
|
|
}
|
|
|
|
// CreditWheelReward 在同一事务里完成转盘金币奖品入账、交易分录和钱包 outbox。
|
|
func (r *Repository) CreditWheelReward(ctx context.Context, command ledger.WheelRewardCommand) (ledger.WheelRewardReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.WheelRewardReceipt{}, 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.WheelRewardReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := wheelRewardRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeWheelReward); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
return r.receiptForWheelRewardTransaction(ctx, tx, txRow.TransactionID)
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs)
|
|
if err != nil {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount + command.Amount
|
|
metadata := wheelRewardMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
Amount: command.Amount,
|
|
DrawID: command.DrawID,
|
|
WheelID: command.WheelID,
|
|
SelectedTierID: command.SelectedTierID,
|
|
VisibleRegionID: command.VisibleRegionID,
|
|
Reason: command.Reason,
|
|
BalanceAfter: balanceAfter,
|
|
GrantedAtMS: nowMs,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeWheelReward, requestHash, command.DrawID, metadata, nowMs); err != nil {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balanceAfter,
|
|
FrozenAfter: account.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
|
wheelRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
|
}); err != nil {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.WheelRewardReceipt{}, err
|
|
}
|
|
|
|
return receiptFromWheelRewardMetadata(transactionID, metadata), nil
|
|
}
|
|
|
|
// CreditRoomTurnoverReward 在同一事务里完成每周房间流水奖励 COIN 入账、交易分录和钱包 outbox。
|
|
func (r *Repository) CreditRoomTurnoverReward(ctx context.Context, command ledger.RoomTurnoverRewardCommand) (ledger.RoomTurnoverRewardReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, 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.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := roomTurnoverRewardRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeRoomTurnoverReward); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
|
|
return r.receiptForRoomTurnoverRewardTransaction(ctx, tx, txRow.TransactionID)
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs)
|
|
if err != nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount + command.Amount
|
|
|
|
metadata := roomTurnoverRewardMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
Amount: command.Amount,
|
|
SettlementID: command.SettlementID,
|
|
RoomID: command.RoomID,
|
|
PeriodStartMS: command.PeriodStartMS,
|
|
PeriodEndMS: command.PeriodEndMS,
|
|
CoinSpent: command.CoinSpent,
|
|
TierID: command.TierID,
|
|
TierCode: command.TierCode,
|
|
Reason: command.Reason,
|
|
BalanceAfter: balanceAfter,
|
|
GrantedAtMS: nowMs,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeRoomTurnoverReward, requestHash, command.SettlementID, metadata, nowMs); err != nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balanceAfter,
|
|
FrozenAfter: account.FrozenAmount,
|
|
RoomID: command.RoomID,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
|
roomTurnoverRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
|
}); err != nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.RoomTurnoverRewardReceipt{}, err
|
|
}
|
|
|
|
return receiptFromRoomTurnoverRewardMetadata(transactionID, metadata), nil
|
|
}
|
|
|
|
// CreditInviteActivityReward 在同一事务内完成邀请活动 COIN 奖励入账、交易分录和钱包 outbox。
|
|
func (r *Repository) CreditInviteActivityReward(ctx context.Context, command ledger.InviteActivityRewardCommand) (ledger.InviteActivityRewardReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.InviteActivityRewardReceipt{}, 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.InviteActivityRewardReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := inviteActivityRewardRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeInviteActivityReward); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
|
|
return r.receiptForInviteActivityRewardTransaction(ctx, tx, txRow.TransactionID)
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs)
|
|
if err != nil {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount + command.Amount
|
|
metadata := inviteActivityRewardMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
Amount: command.Amount,
|
|
ClaimID: command.ClaimID,
|
|
RewardType: command.RewardType,
|
|
TierID: command.TierID,
|
|
TierCode: command.TierCode,
|
|
CycleKey: command.CycleKey,
|
|
ReachedValue: command.ReachedValue,
|
|
Reason: command.Reason,
|
|
BalanceAfter: balanceAfter,
|
|
GrantedAtMS: nowMs,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeInviteActivityReward, requestHash, command.ClaimID, metadata, nowMs); err != nil {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balanceAfter,
|
|
FrozenAfter: account.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
|
inviteActivityRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
|
}); err != nil {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.InviteActivityRewardReceipt{}, err
|
|
}
|
|
|
|
return receiptFromInviteActivityRewardMetadata(transactionID, metadata), nil
|
|
}
|
|
|
|
// CreditAgencyOpeningReward 在同一事务内完成代理开业流水档位 COIN 奖励入账、交易分录和钱包 outbox。
|
|
func (r *Repository) CreditAgencyOpeningReward(ctx context.Context, command ledger.AgencyOpeningRewardCommand) (ledger.AgencyOpeningRewardReceipt, error) {
|
|
if r == nil || r.db == nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, 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.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
requestHash := agencyOpeningRewardRequestHash(command)
|
|
if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeAgencyOpeningReward); err != nil || exists {
|
|
if err != nil || !exists {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
return r.receiptForAgencyOpeningRewardTransaction(ctx, tx, txRow.TransactionID)
|
|
}
|
|
|
|
nowMs := time.Now().UnixMilli()
|
|
account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs)
|
|
if err != nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
|
balanceAfter := account.AvailableAmount + command.Amount
|
|
metadata := agencyOpeningRewardMetadata{
|
|
AppCode: command.AppCode,
|
|
TargetUserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
Amount: command.Amount,
|
|
ApplicationID: command.ApplicationID,
|
|
CycleID: command.CycleID,
|
|
AgencyID: command.AgencyID,
|
|
RankNo: command.RankNo,
|
|
ScoreCoins: command.ScoreCoins,
|
|
Reason: command.Reason,
|
|
BalanceAfter: balanceAfter,
|
|
GrantedAtMS: nowMs,
|
|
}
|
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeAgencyOpeningReward, requestHash, command.ApplicationID, metadata, nowMs); err != nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
|
TransactionID: transactionID,
|
|
UserID: command.TargetUserID,
|
|
AssetType: ledger.AssetCoin,
|
|
AvailableDelta: command.Amount,
|
|
FrozenDelta: 0,
|
|
AvailableAfter: balanceAfter,
|
|
FrozenAfter: account.FrozenAmount,
|
|
CreatedAtMS: nowMs,
|
|
}); err != nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
|
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
|
agencyOpeningRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
|
}); err != nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
return ledger.AgencyOpeningRewardReceipt{}, err
|
|
}
|
|
|
|
return receiptFromAgencyOpeningRewardMetadata(transactionID, metadata), nil
|
|
}
|
|
|
|
func cpBreakupFeeRequestHash(command ledger.CPBreakupFeeCommand) string {
|
|
|
|
return stableHash(fmt.Sprintf("cp_breakup_fee|%s|%d|%s|%s|%d",
|
|
appcode.Normalize(command.AppCode),
|
|
command.UserID,
|
|
strings.TrimSpace(command.RelationshipID),
|
|
strings.ToLower(strings.TrimSpace(command.RelationType)),
|
|
command.Amount,
|
|
))
|
|
}
|
|
|
|
func wheelDrawDebitRequestHash(command ledger.WheelDrawDebitCommand) string {
|
|
|
|
return stableHash(fmt.Sprintf("wheel_draw_debit|%s|%d|%s|%d|%d",
|
|
appcode.Normalize(command.AppCode),
|
|
command.UserID,
|
|
strings.TrimSpace(command.WheelID),
|
|
command.DrawCount,
|
|
command.Amount,
|
|
))
|
|
}
|
|
|
|
func taskRewardRequestHash(command ledger.TaskRewardCommand) string {
|
|
return stableHash(fmt.Sprintf("task_reward|%s|%d|%s|%d|%s|%s|%s|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
strings.ToUpper(strings.TrimSpace(command.AssetType)),
|
|
command.Amount,
|
|
command.TaskType,
|
|
command.TaskID,
|
|
command.CycleKey,
|
|
strings.TrimSpace(command.Reason),
|
|
))
|
|
}
|
|
|
|
func luckyGiftRewardRequestHash(command ledger.LuckyGiftRewardCommand) string {
|
|
return stableHash(fmt.Sprintf("lucky_gift_reward|%s|%d|%d|%s|%s|%d|%d|%s|%s|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
command.Amount,
|
|
strings.TrimSpace(command.DrawID),
|
|
strings.TrimSpace(command.RoomID),
|
|
command.VisibleRegionID,
|
|
command.CountryID,
|
|
strings.TrimSpace(command.GiftID),
|
|
strings.TrimSpace(command.PoolID),
|
|
strings.TrimSpace(command.Reason),
|
|
))
|
|
}
|
|
|
|
func wheelRewardRequestHash(command ledger.WheelRewardCommand) string {
|
|
return stableHash(fmt.Sprintf("wheel_reward|%s|%d|%d|%s|%s|%s|%d|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
command.Amount,
|
|
strings.TrimSpace(command.DrawID),
|
|
strings.TrimSpace(command.WheelID),
|
|
strings.TrimSpace(command.SelectedTierID),
|
|
command.VisibleRegionID,
|
|
strings.TrimSpace(command.Reason),
|
|
))
|
|
}
|
|
|
|
func roomTurnoverRewardRequestHash(command ledger.RoomTurnoverRewardCommand) string {
|
|
return stableHash(fmt.Sprintf("room_turnover_reward|%s|%d|%d|%s|%s|%d|%d|%d|%d|%s|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
command.Amount,
|
|
strings.TrimSpace(command.SettlementID),
|
|
strings.TrimSpace(command.RoomID),
|
|
command.PeriodStartMS,
|
|
command.PeriodEndMS,
|
|
command.CoinSpent,
|
|
command.TierID,
|
|
strings.TrimSpace(command.TierCode),
|
|
strings.TrimSpace(command.Reason),
|
|
))
|
|
}
|
|
|
|
func inviteActivityRewardRequestHash(command ledger.InviteActivityRewardCommand) string {
|
|
return stableHash(fmt.Sprintf("invite_activity_reward|%s|%d|%d|%s|%s|%d|%s|%s|%d|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
command.Amount,
|
|
strings.TrimSpace(command.ClaimID),
|
|
strings.TrimSpace(command.RewardType),
|
|
command.TierID,
|
|
strings.TrimSpace(command.TierCode),
|
|
strings.TrimSpace(command.CycleKey),
|
|
command.ReachedValue,
|
|
strings.TrimSpace(command.Reason),
|
|
))
|
|
}
|
|
|
|
func agencyOpeningRewardRequestHash(command ledger.AgencyOpeningRewardCommand) string {
|
|
return stableHash(fmt.Sprintf("agency_opening_reward|%s|%d|%d|%s|%s|%d|%d|%d|%s",
|
|
appcode.Normalize(command.AppCode),
|
|
command.TargetUserID,
|
|
command.Amount,
|
|
strings.TrimSpace(command.ApplicationID),
|
|
strings.TrimSpace(command.CycleID),
|
|
command.AgencyID,
|
|
command.RankNo,
|
|
command.ScoreCoins,
|
|
strings.TrimSpace(command.Reason),
|
|
))
|
|
}
|