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

@@ -153,30 +153,64 @@ func (s *order) Pay(ctx context.Context, id string, form *transaction_dto.OrderP
return s.payWithBalance(ctx, o)
}
// External payment (mock) - normally returns URL/params
return &transaction_dto.OrderPayResponse{
PayParams: "mock_pay_params",
}, nil
}
// ProcessExternalPayment handles callback from payment gateway
func (s *order) ProcessExternalPayment(ctx context.Context, orderID, externalID string) error {
oid := cast.ToInt64(orderID)
o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
if err != nil {
return errorx.ErrRecordNotFound
}
if o.Status != consts.OrderStatusCreated {
return nil // Already processed idempotency
}
return s.settleOrder(ctx, o, "external", externalID)
}
func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transaction_dto.OrderPayResponse, error) {
err := s.settleOrder(ctx, o, "balance", "")
if err != nil {
if _, ok := err.(*errorx.AppError); ok {
return nil, err
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
return &transaction_dto.OrderPayResponse{
PayParams: "balance_paid",
}, nil
}
func (s *order) settleOrder(ctx context.Context, o *models.Order, method, externalID string) error {
var tenantOwnerID int64
err := models.Q.Transaction(func(tx *models.Query) error {
// 1. Deduct User Balance
info, err := tx.User.WithContext(ctx).
Where(tx.User.ID.Eq(o.UserID), 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("余额不足")
// 1. Deduct User Balance (Only for balance method)
if method == "balance" {
info, err := tx.User.WithContext(ctx).
Where(tx.User.ID.Eq(o.UserID), 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. Update Order Status
now := time.Now()
_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(o.ID)).Updates(&models.Order{
Status: consts.OrderStatusPaid,
PaidAt: now,
// snapshot := o.Snapshot // Preserve existing snapshot or update it with external ID
// TODO: Update snapshot with payment info
_, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(o.ID)).Updates(&models.Order{
Status: consts.OrderStatusPaid,
PaidAt: now,
UpdatedAt: now,
})
if err != nil {
return err
@@ -210,18 +244,31 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
}
tenantOwnerID = t.UserID
// Calculate Commission
amount := o.AmountPaid
fee := int64(float64(amount) * 0.10)
creatorIncome := amount - fee
// Credit Tenant Owner Balance (Net Income)
_, err = tx.User.WithContext(ctx).
Where(tx.User.ID.Eq(tenantOwnerID)).
Update(tx.User.Balance, gorm.Expr("balance + ?", creatorIncome))
if err != nil {
return err
}
ledger := &models.TenantLedger{
TenantID: o.TenantID,
UserID: t.UserID, // Owner
OrderID: o.ID,
Type: consts.TenantLedgerTypeDebitPurchase, // Income from purchase
Amount: o.AmountPaid,
BalanceBefore: 0, // TODO: Fetch previous balance if tracking tenant balance
Amount: creatorIncome,
BalanceBefore: 0, // TODO
BalanceAfter: 0, // TODO
FrozenBefore: 0,
FrozenAfter: 0,
IdempotencyKey: uuid.NewString(),
Remark: "内容销售收入",
Remark: "内容销售收入 (扣除平台费)",
OperatorUserID: o.UserID,
}
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
@@ -231,10 +278,7 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
return nil
})
if err != nil {
if _, ok := err.(*errorx.AppError); ok {
return nil, err
}
return nil, errorx.ErrDatabaseError.WithCause(err)
return err
}
if Notification != nil {
@@ -243,10 +287,7 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
_ = Notification.Send(ctx, tenantOwnerID, "order", "新的订单", "您的店铺有新的订单,收入已入账。")
}
}
return &transaction_dto.OrderPayResponse{
PayParams: "balance_paid",
}, nil
return nil
}
func (s *order) Status(ctx context.Context, id string) (*transaction_dto.OrderStatusResponse, error) {