fix: backport passkey and topup security patches

This commit is contained in:
OpenClaw Task Bot
2026-04-27 08:52:29 +08:00
parent 75bda2e845
commit 0321101ff7
11 changed files with 284 additions and 38 deletions

View File

@@ -23,6 +23,8 @@ type TopUp struct {
Status string `json:"status"`
}
var ErrPaymentMethodMismatch = errors.New("payment method mismatch")
func (topUp *TopUp) Insert() error {
var err error
err = DB.Create(topUp).Error
@@ -55,7 +57,7 @@ func GetTopUpByTradeNo(tradeNo string) *TopUp {
return topUp
}
func Recharge(referenceId string, customerId string) (err error) {
func Recharge(referenceId string, customerId string, callerIp string) (err error) {
if referenceId == "" {
return errors.New("未提供支付单号")
}
@@ -74,6 +76,10 @@ func Recharge(referenceId string, customerId string) (err error) {
return errors.New("充值订单不存在")
}
if topUp.PaymentMethod != "stripe" {
return ErrPaymentMethodMismatch
}
if topUp.Status != common.TopUpStatusPending {
return errors.New("充值订单状态错误")
}
@@ -99,7 +105,7 @@ func Recharge(referenceId string, customerId string) (err error) {
return errors.New("充值失败,请稍后重试")
}
RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v支付金额%d", logger.FormatQuota(int(quota)), topUp.Amount))
RecordTopupLog(topUp.UserId, fmt.Sprintf("使用在线充值成功,充值金额: %v支付金额%d", logger.FormatQuota(int(quota)), topUp.Amount), callerIp, topUp.PaymentMethod, "stripe")
return nil
}
@@ -236,7 +242,7 @@ func SearchAllTopUps(keyword string, pageInfo *common.PageInfo) (topups []*TopUp
}
// ManualCompleteTopUp 管理员手动完成订单并给用户充值
func ManualCompleteTopUp(tradeNo string) error {
func ManualCompleteTopUp(tradeNo string, callerIp string) error {
if tradeNo == "" {
return errors.New("未提供订单号")
}
@@ -249,15 +255,14 @@ func ManualCompleteTopUp(tradeNo string) error {
var userId int
var quotaToAdd int
var payMoney float64
var paymentMethod string
err := DB.Transaction(func(tx *gorm.DB) error {
topUp := &TopUp{}
// 行级锁,避免并发补单
if err := tx.Set("gorm:query_option", "FOR UPDATE").Where(refCol+" = ?", tradeNo).First(topUp).Error; err != nil {
return errors.New("充值订单不存在")
}
// 幂等处理:已成功直接返回
if topUp.Status == common.TopUpStatusSuccess {
return nil
}
@@ -266,9 +271,6 @@ func ManualCompleteTopUp(tradeNo string) error {
return errors.New("订单状态不是待支付,无法补单")
}
// 计算应充值额度:
// - Stripe 订单Money 代表经分组倍率换算后的美元数量,直接 * QuotaPerUnit
// - 其他订单如易支付Amount 为美元数量,* QuotaPerUnit
if topUp.PaymentMethod == "stripe" {
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
quotaToAdd = int(decimal.NewFromFloat(topUp.Money).Mul(dQuotaPerUnit).IntPart())
@@ -281,20 +283,19 @@ func ManualCompleteTopUp(tradeNo string) error {
return errors.New("无效的充值额度")
}
// 标记完成
topUp.CompleteTime = common.GetTimestamp()
topUp.Status = common.TopUpStatusSuccess
if err := tx.Save(topUp).Error; err != nil {
return err
}
// 增加用户额度(立即写库,保持一致性)
if err := tx.Model(&User{}).Where("id = ?", topUp.UserId).Update("quota", gorm.Expr("quota + ?", quotaToAdd)).Error; err != nil {
return err
}
userId = topUp.UserId
payMoney = topUp.Money
paymentMethod = topUp.PaymentMethod
return nil
})
@@ -302,11 +303,11 @@ func ManualCompleteTopUp(tradeNo string) error {
return err
}
// 事务外记录日志,避免阻塞
RecordLog(userId, LogTypeTopup, fmt.Sprintf("管理员补单成功,充值金额: %v支付金额%f", logger.FormatQuota(quotaToAdd), payMoney))
RecordTopupLog(userId, fmt.Sprintf("管理员补单成功,充值金额: %v支付金额%f", logger.FormatQuota(quotaToAdd), payMoney), callerIp, paymentMethod, "admin")
return nil
}
func RechargeCreem(referenceId string, customerEmail string, customerName string) (err error) {
func RechargeCreem(referenceId string, customerEmail string, customerName string, callerIp string) (err error) {
if referenceId == "" {
return errors.New("未提供支付单号")
}
@@ -325,6 +326,10 @@ func RechargeCreem(referenceId string, customerEmail string, customerName string
return errors.New("充值订单不存在")
}
if topUp.PaymentMethod != "creem" {
return ErrPaymentMethodMismatch
}
if topUp.Status != common.TopUpStatusPending {
return errors.New("充值订单状态错误")
}
@@ -336,24 +341,18 @@ func RechargeCreem(referenceId string, customerEmail string, customerName string
return err
}
// Creem 直接使用 Amount 作为充值额度(整数)
quota = topUp.Amount
// 构建更新字段,优先使用邮箱,如果邮箱为空则使用用户名
updateFields := map[string]interface{}{
"quota": gorm.Expr("quota + ?", quota),
}
// 如果有客户邮箱,尝试更新用户邮箱(仅当用户邮箱为空时)
if customerEmail != "" {
// 先检查用户当前邮箱是否为空
var user User
err = tx.Where("id = ?", topUp.UserId).First(&user).Error
if err != nil {
return err
}
// 如果用户邮箱为空,则更新为支付时使用的邮箱
if user.Email == "" {
updateFields["email"] = customerEmail
}
@@ -372,12 +371,12 @@ func RechargeCreem(referenceId string, customerEmail string, customerName string
return errors.New("充值失败,请稍后重试")
}
RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功充值额度: %v支付金额%.2f", quota, topUp.Money))
RecordTopupLog(topUp.UserId, fmt.Sprintf("使用Creem充值成功充值额度: %v支付金额%.2f", quota, topUp.Money), callerIp, topUp.PaymentMethod, "creem")
return nil
}
func RechargeWaffo(tradeNo string) (err error) {
func RechargeWaffo(tradeNo string, callerIp string) (err error) {
if tradeNo == "" {
return errors.New("未提供支付单号")
}
@@ -396,8 +395,12 @@ func RechargeWaffo(tradeNo string) (err error) {
return errors.New("充值订单不存在")
}
if topUp.PaymentMethod != "waffo" {
return ErrPaymentMethodMismatch
}
if topUp.Status == common.TopUpStatusSuccess {
return nil // 幂等:已成功直接返回
return nil
}
if topUp.Status != common.TopUpStatusPending {
@@ -430,7 +433,7 @@ func RechargeWaffo(tradeNo string) (err error) {
}
if quotaToAdd > 0 {
RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("Waffo充值成功充值额度: %v支付金额: %.2f", logger.FormatQuota(quotaToAdd), topUp.Money))
RecordTopupLog(topUp.UserId, fmt.Sprintf("Waffo充值成功充值额度: %v支付金额: %.2f", logger.FormatQuota(quotaToAdd), topUp.Money), callerIp, topUp.PaymentMethod, "waffo")
}
return nil