mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
裁剪
swagger 移除 merchant swagger 改造首页内容为通用后台管理系统定位 - 修改首页标题从 'LINUX DO Credit' 改为 'Modern Platform' - 更新副标题为 '为二次开发而生' - 更新首页描述为通用平台的特点 - 更新首页特性标签为 '开箱即用、高度可扩展、工业级基建' - 修改展示卡片为技术栈和二次开发相关 - 更新开发者示例代码为通用的注册和 API Key 获取示例 - 更新页脚品牌名为 'Modern Platform' - 调整页脚导航链接为通用平台相关内容 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 去除遗留 裁剪 移除 /api/v1/user/pay-key 相关代码 - 删除后端 UpdatePayKey 处理器函数和 UpdatePayKeyRequest 结构体 - 删除 User 模型中的 PayKey 字段 - 删除 User.VerifyPayKey 方法 - 删除 EncryptPayKeyFailed 错误常量 - 删除 /api/v1/user/pay-key PUT 路由 - 删除 OAuth 返回中的 IsPayKey 字段 - 删除前端 UserService.updatePayKey 方法 - 删除前端所有支付密钥 UI 和逻辑 - 更新相关的导出和注释 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 去除遗留 api 修正 系统配置 前端裁剪 后端裁剪 init
This commit is contained in:
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 linux.do
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/linux-do/credit/internal/common"
|
||||
"github.com/linux-do/credit/internal/model"
|
||||
"github.com/linux-do/credit/internal/task"
|
||||
"github.com/linux-do/credit/internal/task/scheduler"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// BalanceOperation 余额操作类型
|
||||
type BalanceOperation int
|
||||
|
||||
const (
|
||||
BalanceAdd BalanceOperation = iota
|
||||
BalanceDeduct
|
||||
)
|
||||
|
||||
// BalanceUpdateOptions 余额更新选项
|
||||
type BalanceUpdateOptions struct {
|
||||
UserID uint64
|
||||
Amount decimal.Decimal
|
||||
Operation BalanceOperation
|
||||
ScoreChange int64
|
||||
TotalField string // 累计字段:total_payment / total_receive / total_transfer
|
||||
CheckBalance bool
|
||||
AsyncTransfer bool // 异步结算时使用 pending_balance 字段
|
||||
}
|
||||
|
||||
// UpdateBalance 通用余额更新函数
|
||||
func UpdateBalance(tx *gorm.DB, opts BalanceUpdateOptions) error {
|
||||
updates := make(map[string]interface{})
|
||||
|
||||
balanceField := "available_balance"
|
||||
if opts.AsyncTransfer {
|
||||
balanceField = "pending_balance"
|
||||
}
|
||||
|
||||
if opts.Operation == BalanceAdd {
|
||||
updates[balanceField] = gorm.Expr(balanceField+" + ?", opts.Amount)
|
||||
} else {
|
||||
updates[balanceField] = gorm.Expr(balanceField+" - ?", opts.Amount)
|
||||
}
|
||||
|
||||
if opts.TotalField != "" {
|
||||
updates[opts.TotalField] = gorm.Expr(opts.TotalField+" + ?", opts.Amount)
|
||||
}
|
||||
|
||||
if opts.ScoreChange != 0 {
|
||||
updates["pay_score"] = gorm.Expr("pay_score + ?", opts.ScoreChange)
|
||||
}
|
||||
|
||||
query := tx.Model(&model.User{}).Where("id = ?", opts.UserID)
|
||||
if opts.CheckBalance {
|
||||
query = query.Where(balanceField+" >= ?", opts.Amount)
|
||||
}
|
||||
|
||||
result := query.UpdateColumns(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if opts.CheckBalance && result.RowsAffected == 0 {
|
||||
return errors.New(common.InsufficientBalance)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SettlePendingToAvailable 将资金从 PendingBalance 转入 AvailableBalance
|
||||
func SettlePendingToAvailable(tx *gorm.DB, userID uint64, amount decimal.Decimal) error {
|
||||
result := tx.Model(&model.User{}).
|
||||
Where("id = ? AND pending_balance >= ?", userID, amount).
|
||||
UpdateColumns(map[string]interface{}{
|
||||
"pending_balance": gorm.Expr("pending_balance - ?", amount),
|
||||
"available_balance": gorm.Expr("available_balance + ?", amount),
|
||||
})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New(common.InsufficientBalance)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDailyLimit 检查用户每日支付限额
|
||||
// 返回 nil 表示未超限额,返回 error 表示超限或查询失败
|
||||
func CheckDailyLimit(tx *gorm.DB, userID uint64, amount decimal.Decimal, dailyLimit *int64) error {
|
||||
if dailyLimit == nil || *dailyLimit <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
datePart := int64(now.Year()*10000 + int(now.Month())*100 + now.Day())
|
||||
lockID := int64(userID)*100000000 + datePart
|
||||
if err := tx.Exec("SELECT pg_advisory_xact_lock(?)", lockID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
todayUsed, err := GetTodayUsedAmount(tx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if todayUsed.Add(amount).GreaterThan(decimal.NewFromInt(*dailyLimit)) {
|
||||
return errors.New(common.DailyLimitExceeded)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTodayUsedAmount 获取用户当日已使用的支付额度
|
||||
func GetTodayUsedAmount(db *gorm.DB, userID uint64) (decimal.Decimal, error) {
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
todayEnd := todayStart.Add(24 * time.Hour)
|
||||
|
||||
var total decimal.Decimal
|
||||
err := db.Model(&model.Order{}).
|
||||
Where("payer_user_id = ? AND status = ? AND type IN ? AND trade_time >= ? AND trade_time < ?",
|
||||
userID,
|
||||
model.OrderStatusSuccess,
|
||||
[]model.OrderType{model.OrderTypePayment, model.OrderTypeOnline, model.OrderTypeDistribute, model.OrderTypeTransfer},
|
||||
todayStart,
|
||||
todayEnd).
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&total).Error
|
||||
|
||||
return total, err
|
||||
}
|
||||
|
||||
// CalculateFee 计算手续费和商户实收金额
|
||||
// 返回:手续费、商户实收金额、手续费百分比
|
||||
func CalculateFee(amount decimal.Decimal, feeRate decimal.Decimal) (fee decimal.Decimal, merchantAmount decimal.Decimal, feePercent int64) {
|
||||
fee = amount.Mul(feeRate).Round(2)
|
||||
merchantAmount = amount.Sub(fee)
|
||||
feePercent = feeRate.Mul(decimal.NewFromInt(100)).IntPart()
|
||||
return
|
||||
}
|
||||
|
||||
// ValidateTestModePayment 验证测试模式下的支付权限
|
||||
// 返回 error:nil 表示允许支付,非 nil 表示拒绝支付
|
||||
func ValidateTestModePayment(currentUserID, merchantUserID uint64, isTestMode bool) error {
|
||||
if currentUserID == merchantUserID {
|
||||
if !isTestMode {
|
||||
return errors.New(common.CannotPaySelf)
|
||||
}
|
||||
} else if isTestMode {
|
||||
return errors.New(common.TestModeCannotProcessOrder)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnqueueMerchantNotify 下发商户回调任务
|
||||
func EnqueueMerchantNotify(orderID uint64, clientID string) error {
|
||||
notifyPayload, _ := json.Marshal(map[string]interface{}{
|
||||
"order_id": orderID,
|
||||
"client_id": clientID,
|
||||
})
|
||||
if _, err := scheduler.AsynqClient.Enqueue(
|
||||
asynq.NewTask(task.MerchantPaymentNotifyTask, notifyPayload),
|
||||
asynq.Queue(task.QueueWebhook),
|
||||
asynq.MaxRetry(10),
|
||||
asynq.Timeout(30*time.Second),
|
||||
); err != nil {
|
||||
return fmt.Errorf("下发商户回调任务失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user