feat: 实现平台抽成、提现审批、异步任务集成及安全审计功能
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user