feat: 实现平台抽成、提现审批、异步任务集成及安全审计功能

This commit is contained in:
2025-12-30 14:54:19 +08:00
parent 5e8dbec806
commit ee1acae3ed
25 changed files with 985 additions and 60 deletions

View File

@@ -7,6 +7,7 @@ import (
"quyun/v2/app/errorx"
creator_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/database/fields"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
@@ -302,8 +303,103 @@ func (s *creator) ListOrders(ctx context.Context, filter *creator_dto.CreatorOrd
}
func (s *creator) ProcessRefund(ctx context.Context, id string, form *creator_dto.RefundForm) error {
// Complex logic involving ledgers and order status update
return nil
tid, err := s.getTenantID(ctx)
if err != nil {
return err
}
oid := cast.ToInt64(id)
uid := cast.ToInt64(ctx.Value(consts.CtxKeyUser)) // Creator ID
// Fetch Order
o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid), models.OrderQuery.TenantID.Eq(tid)).First()
if err != nil {
return errorx.ErrRecordNotFound
}
// Validate Status
// Allow refunding 'refunding' orders. Or 'paid' if we treat this as "Initiate Refund".
// Given "Action" (accept/reject), assume 'refunding'.
if o.Status != consts.OrderStatusRefunding {
return errorx.ErrStatusConflict.WithMsg("订单状态不是退款中")
}
if form.Action == "reject" {
_, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).Updates(&models.Order{
Status: consts.OrderStatusPaid,
RefundReason: form.Reason, // Store reject reason? Or clear it?
})
return err
}
if form.Action == "accept" {
return models.Q.Transaction(func(tx *models.Query) error {
// 1. Deduct Creator Balance
// We credited Creator User Balance in Order.Pay. Now deduct it.
info, err := tx.User.WithContext(ctx).
Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(o.AmountPaid)).
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
if err != nil {
return err
}
if info.RowsAffected == 0 {
return errorx.ErrQuotaExceeded.WithMsg("余额不足,无法退款")
}
// 2. Credit Buyer Balance
_, err = tx.User.WithContext(ctx).
Where(tx.User.ID.Eq(o.UserID)).
Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
if err != nil {
return err
}
// 3. Update Order Status
_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(oid)).Updates(&models.Order{
Status: consts.OrderStatusRefunded,
RefundedAt: time.Now(),
RefundOperatorUserID: uid,
RefundReason: form.Reason,
})
if err != nil {
return err
}
// 4. Revoke Content Access
// Fetch order items to get content IDs
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(oid)).Find()
contentIDs := make([]int64, len(items))
for i, item := range items {
contentIDs[i] = item.ContentID
}
if len(contentIDs) > 0 {
_, err = tx.ContentAccess.WithContext(ctx).
Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.In(contentIDs...)).
UpdateSimple(tx.ContentAccess.Status.Value(consts.ContentAccessStatusRevoked))
if err != nil {
return err
}
}
// 5. Create Tenant Ledger
ledger := &models.TenantLedger{
TenantID: tid,
UserID: uid,
OrderID: oid,
Type: consts.TenantLedgerTypeCreditRefund,
Amount: o.AmountPaid,
Remark: "退款: " + form.Reason,
OperatorUserID: uid,
IdempotencyKey: uuid.NewString(),
}
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
return err
}
return nil
})
}
return errorx.ErrBadRequest.WithMsg("无效的操作")
}
func (s *creator) GetSettings(ctx context.Context) (*creator_dto.Settings, error) {
@@ -327,19 +423,131 @@ func (s *creator) UpdateSettings(ctx context.Context, form *creator_dto.Settings
}
func (s *creator) ListPayoutAccounts(ctx context.Context) ([]creator_dto.PayoutAccount, error) {
return []creator_dto.PayoutAccount{}, nil
tid, err := s.getTenantID(ctx)
if err != nil {
return nil, err
}
list, err := models.PayoutAccountQuery.WithContext(ctx).Where(models.PayoutAccountQuery.TenantID.Eq(tid)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
var data []creator_dto.PayoutAccount
for _, v := range list {
data = append(data, creator_dto.PayoutAccount{
ID: cast.ToString(v.ID),
Type: v.Type,
Name: v.Name,
Account: v.Account,
Realname: v.Realname,
})
}
return data, nil
}
func (s *creator) AddPayoutAccount(ctx context.Context, form *creator_dto.PayoutAccount) error {
tid, err := s.getTenantID(ctx)
if err != nil {
return err
}
uid := cast.ToInt64(ctx.Value(consts.CtxKeyUser))
pa := &models.PayoutAccount{
TenantID: tid,
UserID: uid,
Type: form.Type,
Name: form.Name,
Account: form.Account,
Realname: form.Realname,
}
if err := models.PayoutAccountQuery.WithContext(ctx).Create(pa); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
func (s *creator) RemovePayoutAccount(ctx context.Context, id string) error {
tid, err := s.getTenantID(ctx)
if err != nil {
return err
}
pid := cast.ToInt64(id)
_, err = models.PayoutAccountQuery.WithContext(ctx).
Where(models.PayoutAccountQuery.ID.Eq(pid), models.PayoutAccountQuery.TenantID.Eq(tid)).
Delete()
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
return nil
}
func (s *creator) Withdraw(ctx context.Context, form *creator_dto.WithdrawForm) error {
return nil
tid, err := s.getTenantID(ctx)
if err != nil {
return err
}
uid := cast.ToInt64(ctx.Value(consts.CtxKeyUser))
amount := int64(form.Amount * 100)
if amount <= 0 {
return errorx.ErrBadRequest.WithMsg("金额无效")
}
// Validate Payout Account
_, err = models.PayoutAccountQuery.WithContext(ctx).
Where(models.PayoutAccountQuery.ID.Eq(cast.ToInt64(form.AccountID)), models.PayoutAccountQuery.TenantID.Eq(tid)).
First()
if err != nil {
return errorx.ErrRecordNotFound.WithMsg("收款账户不存在")
}
return models.Q.Transaction(func(tx *models.Query) error {
// 1. Deduct Balance
info, err := tx.User.WithContext(ctx).
Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(amount)).
Update(tx.User.Balance, gorm.Expr("balance - ?", amount))
if err != nil {
return err
}
if info.RowsAffected == 0 {
return errorx.ErrQuotaExceeded.WithMsg("余额不足")
}
// 2. Create Order (Withdrawal)
order := &models.Order{
TenantID: tid,
UserID: uid,
Type: consts.OrderTypeWithdrawal,
Status: consts.OrderStatusCreated, // Created = Pending Processing
Currency: consts.CurrencyCNY,
AmountOriginal: amount,
AmountPaid: amount, // Actually Amount Withdrawn
IdempotencyKey: uuid.NewString(),
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}), // Can store account details here
}
if err := tx.Order.WithContext(ctx).Create(order); err != nil {
return err
}
// 3. Create Tenant Ledger
ledger := &models.TenantLedger{
TenantID: tid,
UserID: uid,
OrderID: order.ID,
Type: consts.TenantLedgerTypeCreditWithdrawal,
Amount: amount,
Remark: "提现申请",
OperatorUserID: uid,
IdempotencyKey: uuid.NewString(),
}
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
return err
}
return nil
})
}
// Helpers