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:
ryan
2026-06-07 15:32:14 +08:00
parent 8a782525de
commit 48d414e197
180 changed files with 1538 additions and 27463 deletions
View File
-192
View File
@@ -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
}