Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c47d9fb5
authored
Sep 12, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(payment): add payment settings configuration and update payment methods handling
parent
c9ffa5a6
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
579 additions
and
199 deletions
+579
-199
controller/channel-billing.go
+2
-2
controller/channel.go
+4
-3
controller/misc.go
+4
-8
controller/topup.go
+55
-8
controller/topup_stripe.go
+10
-1
dto/channel_settings.go
+8
-0
model/channel.go
+2
-1
model/option.go
+12
-12
relay/channel/vertex/adaptor.go
+58
-44
router/api-router.go
+1
-0
service/epay.go
+3
-2
setting/operation_setting/payment_setting.go
+23
-0
setting/operation_setting/payment_setting_old.go
+17
-4
web/src/components/settings/PaymentSetting.jsx
+26
-0
web/src/components/table/channels/modals/EditChannelModal.jsx
+54
-15
web/src/components/topup/RechargeCard.jsx
+75
-41
web/src/components/topup/index.jsx
+116
-57
web/src/components/topup/modals/PaymentConfirmModal.jsx
+33
-0
web/src/helpers/data.js
+0
-1
web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx
+76
-0
No files found.
controller/channel-billing.go
View file @
c47d9fb5
...
@@ -10,7 +10,7 @@ import (
...
@@ -10,7 +10,7 @@ import (
"one-api/constant"
"one-api/constant"
"one-api/model"
"one-api/model"
"one-api/service"
"one-api/service"
"one-api/setting"
"one-api/setting
/operation_setting
"
"one-api/types"
"one-api/types"
"strconv"
"strconv"
"time"
"time"
...
@@ -342,7 +342,7 @@ func updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {
...
@@ -342,7 +342,7 @@ func updateChannelMoonshotBalance(channel *model.Channel) (float64, error) {
return
0
,
fmt
.
Errorf
(
"failed to update moonshot balance, status: %v, code: %d, scode: %s"
,
response
.
Status
,
response
.
Code
,
response
.
Scode
)
return
0
,
fmt
.
Errorf
(
"failed to update moonshot balance, status: %v, code: %d, scode: %s"
,
response
.
Status
,
response
.
Code
,
response
.
Scode
)
}
}
availableBalanceCny
:=
response
.
Data
.
AvailableBalance
availableBalanceCny
:=
response
.
Data
.
AvailableBalance
availableBalanceUsd
:=
decimal
.
NewFromFloat
(
availableBalanceCny
)
.
Div
(
decimal
.
NewFromFloat
(
setting
.
Price
))
.
InexactFloat64
()
availableBalanceUsd
:=
decimal
.
NewFromFloat
(
availableBalanceCny
)
.
Div
(
decimal
.
NewFromFloat
(
operation_
setting
.
Price
))
.
InexactFloat64
()
channel
.
UpdateBalance
(
availableBalanceUsd
)
channel
.
UpdateBalance
(
availableBalanceUsd
)
return
availableBalanceUsd
,
nil
return
availableBalanceUsd
,
nil
}
}
...
...
controller/channel.go
View file @
c47d9fb5
...
@@ -6,6 +6,7 @@ import (
...
@@ -6,6 +6,7 @@ import (
"net/http"
"net/http"
"one-api/common"
"one-api/common"
"one-api/constant"
"one-api/constant"
"one-api/dto"
"one-api/model"
"one-api/model"
"strconv"
"strconv"
"strings"
"strings"
...
@@ -560,7 +561,7 @@ func AddChannel(c *gin.Context) {
...
@@ -560,7 +561,7 @@ func AddChannel(c *gin.Context) {
case
"multi_to_single"
:
case
"multi_to_single"
:
addChannelRequest
.
Channel
.
ChannelInfo
.
IsMultiKey
=
true
addChannelRequest
.
Channel
.
ChannelInfo
.
IsMultiKey
=
true
addChannelRequest
.
Channel
.
ChannelInfo
.
MultiKeyMode
=
addChannelRequest
.
MultiKeyMode
addChannelRequest
.
Channel
.
ChannelInfo
.
MultiKeyMode
=
addChannelRequest
.
MultiKeyMode
if
addChannelRequest
.
Channel
.
Type
==
constant
.
ChannelTypeVertexAi
{
if
addChannelRequest
.
Channel
.
Type
==
constant
.
ChannelTypeVertexAi
&&
addChannelRequest
.
Channel
.
GetOtherSettings
()
.
VertexKeyType
!=
dto
.
VertexKeyTypeAPIKey
{
array
,
err
:=
getVertexArrayKeys
(
addChannelRequest
.
Channel
.
Key
)
array
,
err
:=
getVertexArrayKeys
(
addChannelRequest
.
Channel
.
Key
)
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
...
@@ -585,7 +586,7 @@ func AddChannel(c *gin.Context) {
...
@@ -585,7 +586,7 @@ func AddChannel(c *gin.Context) {
}
}
keys
=
[]
string
{
addChannelRequest
.
Channel
.
Key
}
keys
=
[]
string
{
addChannelRequest
.
Channel
.
Key
}
case
"batch"
:
case
"batch"
:
if
addChannelRequest
.
Channel
.
Type
==
constant
.
ChannelTypeVertexAi
{
if
addChannelRequest
.
Channel
.
Type
==
constant
.
ChannelTypeVertexAi
&&
addChannelRequest
.
Channel
.
GetOtherSettings
()
.
VertexKeyType
!=
dto
.
VertexKeyTypeAPIKey
{
// multi json
// multi json
keys
,
err
=
getVertexArrayKeys
(
addChannelRequest
.
Channel
.
Key
)
keys
,
err
=
getVertexArrayKeys
(
addChannelRequest
.
Channel
.
Key
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -840,7 +841,7 @@ func UpdateChannel(c *gin.Context) {
...
@@ -840,7 +841,7 @@ func UpdateChannel(c *gin.Context) {
}
}
// 处理 Vertex AI 的特殊情况
// 处理 Vertex AI 的特殊情况
if
channel
.
Type
==
constant
.
ChannelTypeVertexAi
{
if
channel
.
Type
==
constant
.
ChannelTypeVertexAi
&&
channel
.
GetOtherSettings
()
.
VertexKeyType
!=
dto
.
VertexKeyTypeAPIKey
{
// 尝试解析新密钥为JSON数组
// 尝试解析新密钥为JSON数组
if
strings
.
HasPrefix
(
strings
.
TrimSpace
(
channel
.
Key
),
"["
)
{
if
strings
.
HasPrefix
(
strings
.
TrimSpace
(
channel
.
Key
),
"["
)
{
array
,
err
:=
getVertexArrayKeys
(
channel
.
Key
)
array
,
err
:=
getVertexArrayKeys
(
channel
.
Key
)
...
...
controller/misc.go
View file @
c47d9fb5
...
@@ -59,10 +59,6 @@ func GetStatus(c *gin.Context) {
...
@@ -59,10 +59,6 @@ func GetStatus(c *gin.Context) {
"wechat_qrcode"
:
common
.
WeChatAccountQRCodeImageURL
,
"wechat_qrcode"
:
common
.
WeChatAccountQRCodeImageURL
,
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"server_address"
:
setting
.
ServerAddress
,
"server_address"
:
setting
.
ServerAddress
,
"price"
:
setting
.
Price
,
"stripe_unit_price"
:
setting
.
StripeUnitPrice
,
"min_topup"
:
setting
.
MinTopUp
,
"stripe_min_topup"
:
setting
.
StripeMinTopUp
,
"turnstile_check"
:
common
.
TurnstileCheckEnabled
,
"turnstile_check"
:
common
.
TurnstileCheckEnabled
,
"turnstile_site_key"
:
common
.
TurnstileSiteKey
,
"turnstile_site_key"
:
common
.
TurnstileSiteKey
,
"top_up_link"
:
common
.
TopUpLink
,
"top_up_link"
:
common
.
TopUpLink
,
...
@@ -75,15 +71,15 @@ func GetStatus(c *gin.Context) {
...
@@ -75,15 +71,15 @@ func GetStatus(c *gin.Context) {
"enable_data_export"
:
common
.
DataExportEnabled
,
"enable_data_export"
:
common
.
DataExportEnabled
,
"data_export_default_time"
:
common
.
DataExportDefaultTime
,
"data_export_default_time"
:
common
.
DataExportDefaultTime
,
"default_collapse_sidebar"
:
common
.
DefaultCollapseSidebar
,
"default_collapse_sidebar"
:
common
.
DefaultCollapseSidebar
,
"enable_online_topup"
:
setting
.
PayAddress
!=
""
&&
setting
.
EpayId
!=
""
&&
setting
.
EpayKey
!=
""
,
"enable_stripe_topup"
:
setting
.
StripeApiSecret
!=
""
&&
setting
.
StripeWebhookSecret
!=
""
&&
setting
.
StripePriceId
!=
""
,
"mj_notify_enabled"
:
setting
.
MjNotifyEnabled
,
"mj_notify_enabled"
:
setting
.
MjNotifyEnabled
,
"chats"
:
setting
.
Chats
,
"chats"
:
setting
.
Chats
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
"self_use_mode_enabled"
:
operation_setting
.
SelfUseModeEnabled
,
"self_use_mode_enabled"
:
operation_setting
.
SelfUseModeEnabled
,
"default_use_auto_group"
:
setting
.
DefaultUseAutoGroup
,
"default_use_auto_group"
:
setting
.
DefaultUseAutoGroup
,
"pay_methods"
:
setting
.
PayMethods
,
"usd_exchange_rate"
:
setting
.
USDExchangeRate
,
"usd_exchange_rate"
:
operation_setting
.
USDExchangeRate
,
"price"
:
operation_setting
.
Price
,
"stripe_unit_price"
:
setting
.
StripeUnitPrice
,
// 面板启用开关
// 面板启用开关
"api_info_enabled"
:
cs
.
ApiInfoEnabled
,
"api_info_enabled"
:
cs
.
ApiInfoEnabled
,
...
...
controller/topup.go
View file @
c47d9fb5
...
@@ -9,6 +9,7 @@ import (
...
@@ -9,6 +9,7 @@ import (
"one-api/model"
"one-api/model"
"one-api/service"
"one-api/service"
"one-api/setting"
"one-api/setting"
"one-api/setting/operation_setting"
"strconv"
"strconv"
"sync"
"sync"
"time"
"time"
...
@@ -19,6 +20,44 @@ import (
...
@@ -19,6 +20,44 @@ import (
"github.com/shopspring/decimal"
"github.com/shopspring/decimal"
)
)
func
GetTopUpInfo
(
c
*
gin
.
Context
)
{
// 获取支付方式
payMethods
:=
operation_setting
.
PayMethods
// 如果启用了 Stripe 支付,添加到支付方法列表
if
setting
.
StripeApiSecret
!=
""
&&
setting
.
StripeWebhookSecret
!=
""
&&
setting
.
StripePriceId
!=
""
{
// 检查是否已经包含 Stripe
hasStripe
:=
false
for
_
,
method
:=
range
payMethods
{
if
method
[
"type"
]
==
"stripe"
{
hasStripe
=
true
break
}
}
if
!
hasStripe
{
stripeMethod
:=
map
[
string
]
string
{
"name"
:
"Stripe"
,
"type"
:
"stripe"
,
"color"
:
"rgba(var(--semi-purple-5), 1)"
,
"min_topup"
:
strconv
.
Itoa
(
setting
.
StripeMinTopUp
),
}
payMethods
=
append
(
payMethods
,
stripeMethod
)
}
}
data
:=
gin
.
H
{
"enable_online_topup"
:
operation_setting
.
PayAddress
!=
""
&&
operation_setting
.
EpayId
!=
""
&&
operation_setting
.
EpayKey
!=
""
,
"enable_stripe_topup"
:
setting
.
StripeApiSecret
!=
""
&&
setting
.
StripeWebhookSecret
!=
""
&&
setting
.
StripePriceId
!=
""
,
"pay_methods"
:
payMethods
,
"min_topup"
:
operation_setting
.
MinTopUp
,
"stripe_min_topup"
:
setting
.
StripeMinTopUp
,
"amount_options"
:
operation_setting
.
GetPaymentSetting
()
.
AmountOptions
,
"discount"
:
operation_setting
.
GetPaymentSetting
()
.
AmountDiscount
,
}
common
.
ApiSuccess
(
c
,
data
)
}
type
EpayRequest
struct
{
type
EpayRequest
struct
{
Amount
int64
`json:"amount"`
Amount
int64
`json:"amount"`
PaymentMethod
string
`json:"payment_method"`
PaymentMethod
string
`json:"payment_method"`
...
@@ -31,13 +70,13 @@ type AmountRequest struct {
...
@@ -31,13 +70,13 @@ type AmountRequest struct {
}
}
func
GetEpayClient
()
*
epay
.
Client
{
func
GetEpayClient
()
*
epay
.
Client
{
if
setting
.
PayAddress
==
""
||
setting
.
EpayId
==
""
||
setting
.
EpayKey
==
""
{
if
operation_setting
.
PayAddress
==
""
||
operation_setting
.
EpayId
==
""
||
operation_
setting
.
EpayKey
==
""
{
return
nil
return
nil
}
}
withUrl
,
err
:=
epay
.
NewClient
(
&
epay
.
Config
{
withUrl
,
err
:=
epay
.
NewClient
(
&
epay
.
Config
{
PartnerID
:
setting
.
EpayId
,
PartnerID
:
operation_
setting
.
EpayId
,
Key
:
setting
.
EpayKey
,
Key
:
operation_
setting
.
EpayKey
,
},
setting
.
PayAddress
)
},
operation_
setting
.
PayAddress
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
return
nil
}
}
...
@@ -58,15 +97,23 @@ func getPayMoney(amount int64, group string) float64 {
...
@@ -58,15 +97,23 @@ func getPayMoney(amount int64, group string) float64 {
}
}
dTopupGroupRatio
:=
decimal
.
NewFromFloat
(
topupGroupRatio
)
dTopupGroupRatio
:=
decimal
.
NewFromFloat
(
topupGroupRatio
)
dPrice
:=
decimal
.
NewFromFloat
(
setting
.
Price
)
dPrice
:=
decimal
.
NewFromFloat
(
operation_setting
.
Price
)
// apply optional preset discount by the original request amount (if configured), default 1.0
discount
:=
1.0
if
ds
,
ok
:=
operation_setting
.
GetPaymentSetting
()
.
AmountDiscount
[
int
(
amount
)];
ok
{
if
ds
>
0
{
discount
=
ds
}
}
dDiscount
:=
decimal
.
NewFromFloat
(
discount
)
payMoney
:=
dAmount
.
Mul
(
dPrice
)
.
Mul
(
dTopupGroupRatio
)
payMoney
:=
dAmount
.
Mul
(
dPrice
)
.
Mul
(
dTopupGroupRatio
)
.
Mul
(
dDiscount
)
return
payMoney
.
InexactFloat64
()
return
payMoney
.
InexactFloat64
()
}
}
func
getMinTopup
()
int64
{
func
getMinTopup
()
int64
{
minTopup
:=
setting
.
MinTopUp
minTopup
:=
operation_
setting
.
MinTopUp
if
!
common
.
DisplayInCurrencyEnabled
{
if
!
common
.
DisplayInCurrencyEnabled
{
dMinTopup
:=
decimal
.
NewFromInt
(
int64
(
minTopup
))
dMinTopup
:=
decimal
.
NewFromInt
(
int64
(
minTopup
))
dQuotaPerUnit
:=
decimal
.
NewFromFloat
(
common
.
QuotaPerUnit
)
dQuotaPerUnit
:=
decimal
.
NewFromFloat
(
common
.
QuotaPerUnit
)
...
@@ -99,7 +146,7 @@ func RequestEpay(c *gin.Context) {
...
@@ -99,7 +146,7 @@ func RequestEpay(c *gin.Context) {
return
return
}
}
if
!
setting
.
ContainsPayMethod
(
req
.
PaymentMethod
)
{
if
!
operation_
setting
.
ContainsPayMethod
(
req
.
PaymentMethod
)
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"支付方式不存在"
})
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"支付方式不存在"
})
return
return
}
}
...
...
controller/topup_stripe.go
View file @
c47d9fb5
...
@@ -8,6 +8,7 @@ import (
...
@@ -8,6 +8,7 @@ import (
"one-api/common"
"one-api/common"
"one-api/model"
"one-api/model"
"one-api/setting"
"one-api/setting"
"one-api/setting/operation_setting"
"strconv"
"strconv"
"strings"
"strings"
"time"
"time"
...
@@ -254,6 +255,7 @@ func GetChargedAmount(count float64, user model.User) float64 {
...
@@ -254,6 +255,7 @@ func GetChargedAmount(count float64, user model.User) float64 {
}
}
func
getStripePayMoney
(
amount
float64
,
group
string
)
float64
{
func
getStripePayMoney
(
amount
float64
,
group
string
)
float64
{
originalAmount
:=
amount
if
!
common
.
DisplayInCurrencyEnabled
{
if
!
common
.
DisplayInCurrencyEnabled
{
amount
=
amount
/
common
.
QuotaPerUnit
amount
=
amount
/
common
.
QuotaPerUnit
}
}
...
@@ -262,7 +264,14 @@ func getStripePayMoney(amount float64, group string) float64 {
...
@@ -262,7 +264,14 @@ func getStripePayMoney(amount float64, group string) float64 {
if
topupGroupRatio
==
0
{
if
topupGroupRatio
==
0
{
topupGroupRatio
=
1
topupGroupRatio
=
1
}
}
payMoney
:=
amount
*
setting
.
StripeUnitPrice
*
topupGroupRatio
// apply optional preset discount by the original request amount (if configured), default 1.0
discount
:=
1.0
if
ds
,
ok
:=
operation_setting
.
GetPaymentSetting
()
.
AmountDiscount
[
int
(
originalAmount
)];
ok
{
if
ds
>
0
{
discount
=
ds
}
}
payMoney
:=
amount
*
setting
.
StripeUnitPrice
*
topupGroupRatio
*
discount
return
payMoney
return
payMoney
}
}
...
...
dto/channel_settings.go
View file @
c47d9fb5
...
@@ -9,6 +9,14 @@ type ChannelSettings struct {
...
@@ -9,6 +9,14 @@ type ChannelSettings struct {
SystemPromptOverride
bool
`json:"system_prompt_override,omitempty"`
SystemPromptOverride
bool
`json:"system_prompt_override,omitempty"`
}
}
type
VertexKeyType
string
const
(
VertexKeyTypeJSON
VertexKeyType
=
"json"
VertexKeyTypeAPIKey
VertexKeyType
=
"api_key"
)
type
ChannelOtherSettings
struct
{
type
ChannelOtherSettings
struct
{
AzureResponsesVersion
string
`json:"azure_responses_version,omitempty"`
AzureResponsesVersion
string
`json:"azure_responses_version,omitempty"`
VertexKeyType
VertexKeyType
`json:"vertex_key_type,omitempty"`
// "json" or "api_key"
}
}
model/channel.go
View file @
c47d9fb5
...
@@ -42,7 +42,6 @@ type Channel struct {
...
@@ -42,7 +42,6 @@ type Channel struct {
Priority
*
int64
`json:"priority" gorm:"bigint;default:0"`
Priority
*
int64
`json:"priority" gorm:"bigint;default:0"`
AutoBan
*
int
`json:"auto_ban" gorm:"default:1"`
AutoBan
*
int
`json:"auto_ban" gorm:"default:1"`
OtherInfo
string
`json:"other_info"`
OtherInfo
string
`json:"other_info"`
OtherSettings
string
`json:"settings" gorm:"column:settings"`
// 其他设置
Tag
*
string
`json:"tag" gorm:"index"`
Tag
*
string
`json:"tag" gorm:"index"`
Setting
*
string
`json:"setting" gorm:"type:text"`
// 渠道额外设置
Setting
*
string
`json:"setting" gorm:"type:text"`
// 渠道额外设置
ParamOverride
*
string
`json:"param_override" gorm:"type:text"`
ParamOverride
*
string
`json:"param_override" gorm:"type:text"`
...
@@ -51,6 +50,8 @@ type Channel struct {
...
@@ -51,6 +50,8 @@ type Channel struct {
// add after v0.8.5
// add after v0.8.5
ChannelInfo
ChannelInfo
`json:"channel_info" gorm:"type:json"`
ChannelInfo
ChannelInfo
`json:"channel_info" gorm:"type:json"`
OtherSettings
string
`json:"settings" gorm:"column:settings"`
// 其他设置,存储azure版本等不需要检索的信息,详见dto.ChannelOtherSettings
// cache info
// cache info
Keys
[]
string
`json:"-" gorm:"-"`
Keys
[]
string
`json:"-" gorm:"-"`
}
}
...
...
model/option.go
View file @
c47d9fb5
...
@@ -73,9 +73,9 @@ func InitOptionMap() {
...
@@ -73,9 +73,9 @@ func InitOptionMap() {
common
.
OptionMap
[
"CustomCallbackAddress"
]
=
""
common
.
OptionMap
[
"CustomCallbackAddress"
]
=
""
common
.
OptionMap
[
"EpayId"
]
=
""
common
.
OptionMap
[
"EpayId"
]
=
""
common
.
OptionMap
[
"EpayKey"
]
=
""
common
.
OptionMap
[
"EpayKey"
]
=
""
common
.
OptionMap
[
"Price"
]
=
strconv
.
FormatFloat
(
setting
.
Price
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"Price"
]
=
strconv
.
FormatFloat
(
operation_
setting
.
Price
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"USDExchangeRate"
]
=
strconv
.
FormatFloat
(
setting
.
USDExchangeRate
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"USDExchangeRate"
]
=
strconv
.
FormatFloat
(
operation_
setting
.
USDExchangeRate
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"MinTopUp"
]
=
strconv
.
Itoa
(
setting
.
MinTopUp
)
common
.
OptionMap
[
"MinTopUp"
]
=
strconv
.
Itoa
(
operation_
setting
.
MinTopUp
)
common
.
OptionMap
[
"StripeMinTopUp"
]
=
strconv
.
Itoa
(
setting
.
StripeMinTopUp
)
common
.
OptionMap
[
"StripeMinTopUp"
]
=
strconv
.
Itoa
(
setting
.
StripeMinTopUp
)
common
.
OptionMap
[
"StripeApiSecret"
]
=
setting
.
StripeApiSecret
common
.
OptionMap
[
"StripeApiSecret"
]
=
setting
.
StripeApiSecret
common
.
OptionMap
[
"StripeWebhookSecret"
]
=
setting
.
StripeWebhookSecret
common
.
OptionMap
[
"StripeWebhookSecret"
]
=
setting
.
StripeWebhookSecret
...
@@ -85,7 +85,7 @@ func InitOptionMap() {
...
@@ -85,7 +85,7 @@ func InitOptionMap() {
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
common
.
OptionMap
[
"DefaultUseAutoGroup"
]
=
strconv
.
FormatBool
(
setting
.
DefaultUseAutoGroup
)
common
.
OptionMap
[
"DefaultUseAutoGroup"
]
=
strconv
.
FormatBool
(
setting
.
DefaultUseAutoGroup
)
common
.
OptionMap
[
"PayMethods"
]
=
setting
.
PayMethods2JsonString
()
common
.
OptionMap
[
"PayMethods"
]
=
operation_
setting
.
PayMethods2JsonString
()
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
...
@@ -299,23 +299,23 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -299,23 +299,23 @@ func updateOptionMap(key string, value string) (err error) {
case
"WorkerValidKey"
:
case
"WorkerValidKey"
:
setting
.
WorkerValidKey
=
value
setting
.
WorkerValidKey
=
value
case
"PayAddress"
:
case
"PayAddress"
:
setting
.
PayAddress
=
value
operation_
setting
.
PayAddress
=
value
case
"Chats"
:
case
"Chats"
:
err
=
setting
.
UpdateChatsByJsonString
(
value
)
err
=
setting
.
UpdateChatsByJsonString
(
value
)
case
"AutoGroups"
:
case
"AutoGroups"
:
err
=
setting
.
UpdateAutoGroupsByJsonString
(
value
)
err
=
setting
.
UpdateAutoGroupsByJsonString
(
value
)
case
"CustomCallbackAddress"
:
case
"CustomCallbackAddress"
:
setting
.
CustomCallbackAddress
=
value
operation_
setting
.
CustomCallbackAddress
=
value
case
"EpayId"
:
case
"EpayId"
:
setting
.
EpayId
=
value
operation_
setting
.
EpayId
=
value
case
"EpayKey"
:
case
"EpayKey"
:
setting
.
EpayKey
=
value
operation_
setting
.
EpayKey
=
value
case
"Price"
:
case
"Price"
:
setting
.
Price
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
operation_
setting
.
Price
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"USDExchangeRate"
:
case
"USDExchangeRate"
:
setting
.
USDExchangeRate
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
operation_
setting
.
USDExchangeRate
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"MinTopUp"
:
case
"MinTopUp"
:
setting
.
MinTopUp
,
_
=
strconv
.
Atoi
(
value
)
operation_
setting
.
MinTopUp
,
_
=
strconv
.
Atoi
(
value
)
case
"StripeApiSecret"
:
case
"StripeApiSecret"
:
setting
.
StripeApiSecret
=
value
setting
.
StripeApiSecret
=
value
case
"StripeWebhookSecret"
:
case
"StripeWebhookSecret"
:
...
@@ -413,7 +413,7 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -413,7 +413,7 @@ func updateOptionMap(key string, value string) (err error) {
case
"StreamCacheQueueLength"
:
case
"StreamCacheQueueLength"
:
setting
.
StreamCacheQueueLength
,
_
=
strconv
.
Atoi
(
value
)
setting
.
StreamCacheQueueLength
,
_
=
strconv
.
Atoi
(
value
)
case
"PayMethods"
:
case
"PayMethods"
:
err
=
setting
.
UpdatePayMethodsByJsonString
(
value
)
err
=
operation_
setting
.
UpdatePayMethodsByJsonString
(
value
)
}
}
return
err
return
err
}
}
...
...
relay/channel/vertex/adaptor.go
View file @
c47d9fb5
...
@@ -6,6 +6,7 @@ import (
...
@@ -6,6 +6,7 @@ import (
"fmt"
"fmt"
"io"
"io"
"net/http"
"net/http"
"one-api/common"
"one-api/dto"
"one-api/dto"
"one-api/relay/channel"
"one-api/relay/channel"
"one-api/relay/channel/claude"
"one-api/relay/channel/claude"
...
@@ -80,16 +81,64 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
...
@@ -80,16 +81,64 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}
}
}
}
func
(
a
*
Adaptor
)
GetRequestURL
(
info
*
relaycommon
.
RelayInfo
)
(
string
,
error
)
{
func
(
a
*
Adaptor
)
getRequestUrl
(
info
*
relaycommon
.
RelayInfo
,
modelName
,
suffix
string
)
(
string
,
error
)
{
region
:=
GetModelRegion
(
info
.
ApiVersion
,
info
.
OriginModelName
)
if
info
.
ChannelOtherSettings
.
VertexKeyType
!=
dto
.
VertexKeyTypeAPIKey
{
adc
:=
&
Credentials
{}
adc
:=
&
Credentials
{}
if
err
:=
js
on
.
Unmarshal
([]
byte
(
info
.
ApiKey
),
adc
);
err
!=
nil
{
if
err
:=
comm
on
.
Unmarshal
([]
byte
(
info
.
ApiKey
),
adc
);
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to decode credentials file: %w"
,
err
)
return
""
,
fmt
.
Errorf
(
"failed to decode credentials file: %w"
,
err
)
}
}
region
:=
GetModelRegion
(
info
.
ApiVersion
,
info
.
OriginModelName
)
a
.
AccountCredentials
=
*
adc
a
.
AccountCredentials
=
*
adc
if
a
.
RequestMode
==
RequestModeLlama
{
return
fmt
.
Sprintf
(
"https://%s-aiplatform.googleapis.com/v1beta1/projects/%s/locations/%s/endpoints/openapi/chat/completions"
,
region
,
adc
.
ProjectID
,
region
,
),
nil
}
if
region
==
"global"
{
return
fmt
.
Sprintf
(
"https://aiplatform.googleapis.com/v1/projects/%s/locations/global/publishers/google/models/%s:%s"
,
adc
.
ProjectID
,
modelName
,
suffix
,
),
nil
}
else
{
return
fmt
.
Sprintf
(
"https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:%s"
,
region
,
adc
.
ProjectID
,
region
,
modelName
,
suffix
,
),
nil
}
}
else
{
if
region
==
"global"
{
return
fmt
.
Sprintf
(
"https://aiplatform.googleapis.com/v1/publishers/google/models/%s:%s?key=%s"
,
modelName
,
suffix
,
info
.
ApiKey
,
),
nil
}
else
{
return
fmt
.
Sprintf
(
"https://%s-aiplatform.googleapis.com/v1/publishers/google/models/%s:%s?key=%s"
,
region
,
modelName
,
suffix
,
info
.
ApiKey
,
),
nil
}
}
}
func
(
a
*
Adaptor
)
GetRequestURL
(
info
*
relaycommon
.
RelayInfo
)
(
string
,
error
)
{
suffix
:=
""
suffix
:=
""
if
a
.
RequestMode
==
RequestModeGemini
{
if
a
.
RequestMode
==
RequestModeGemini
{
if
model_setting
.
GetGeminiSettings
()
.
ThinkingAdapterEnabled
{
if
model_setting
.
GetGeminiSettings
()
.
ThinkingAdapterEnabled
{
// 新增逻辑:处理 -thinking-<budget> 格式
// 新增逻辑:处理 -thinking-<budget> 格式
if
strings
.
Contains
(
info
.
UpstreamModelName
,
"-thinking-"
)
{
if
strings
.
Contains
(
info
.
UpstreamModelName
,
"-thinking-"
)
{
...
@@ -112,23 +161,7 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
...
@@ -112,23 +161,7 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
suffix
=
"predict"
suffix
=
"predict"
}
}
if
region
==
"global"
{
return
a
.
getRequestUrl
(
info
,
info
.
UpstreamModelName
,
suffix
)
return
fmt
.
Sprintf
(
"https://aiplatform.googleapis.com/v1/projects/%s/locations/global/publishers/google/models/%s:%s"
,
adc
.
ProjectID
,
info
.
UpstreamModelName
,
suffix
,
),
nil
}
else
{
return
fmt
.
Sprintf
(
"https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:%s"
,
region
,
adc
.
ProjectID
,
region
,
info
.
UpstreamModelName
,
suffix
,
),
nil
}
}
else
if
a
.
RequestMode
==
RequestModeClaude
{
}
else
if
a
.
RequestMode
==
RequestModeClaude
{
if
info
.
IsStream
{
if
info
.
IsStream
{
suffix
=
"streamRawPredict?alt=sse"
suffix
=
"streamRawPredict?alt=sse"
...
@@ -139,41 +172,22 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
...
@@ -139,41 +172,22 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
if
v
,
ok
:=
claudeModelMap
[
info
.
UpstreamModelName
];
ok
{
if
v
,
ok
:=
claudeModelMap
[
info
.
UpstreamModelName
];
ok
{
model
=
v
model
=
v
}
}
if
region
==
"global"
{
return
a
.
getRequestUrl
(
info
,
model
,
suffix
)
return
fmt
.
Sprintf
(
"https://aiplatform.googleapis.com/v1/projects/%s/locations/global/publishers/anthropic/models/%s:%s"
,
adc
.
ProjectID
,
model
,
suffix
,
),
nil
}
else
{
return
fmt
.
Sprintf
(
"https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/anthropic/models/%s:%s"
,
region
,
adc
.
ProjectID
,
region
,
model
,
suffix
,
),
nil
}
}
else
if
a
.
RequestMode
==
RequestModeLlama
{
}
else
if
a
.
RequestMode
==
RequestModeLlama
{
return
fmt
.
Sprintf
(
return
a
.
getRequestUrl
(
info
,
""
,
""
)
"https://%s-aiplatform.googleapis.com/v1beta1/projects/%s/locations/%s/endpoints/openapi/chat/completions"
,
region
,
adc
.
ProjectID
,
region
,
),
nil
}
}
return
""
,
errors
.
New
(
"unsupported request mode"
)
return
""
,
errors
.
New
(
"unsupported request mode"
)
}
}
func
(
a
*
Adaptor
)
SetupRequestHeader
(
c
*
gin
.
Context
,
req
*
http
.
Header
,
info
*
relaycommon
.
RelayInfo
)
error
{
func
(
a
*
Adaptor
)
SetupRequestHeader
(
c
*
gin
.
Context
,
req
*
http
.
Header
,
info
*
relaycommon
.
RelayInfo
)
error
{
channel
.
SetupApiRequestHeader
(
info
,
c
,
req
)
channel
.
SetupApiRequestHeader
(
info
,
c
,
req
)
if
info
.
ChannelOtherSettings
.
VertexKeyType
==
"json"
{
accessToken
,
err
:=
getAccessToken
(
a
,
info
)
accessToken
,
err
:=
getAccessToken
(
a
,
info
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
req
.
Set
(
"Authorization"
,
"Bearer "
+
accessToken
)
req
.
Set
(
"Authorization"
,
"Bearer "
+
accessToken
)
}
return
nil
return
nil
}
}
...
...
router/api-router.go
View file @
c47d9fb5
...
@@ -60,6 +60,7 @@ func SetApiRouter(router *gin.Engine) {
...
@@ -60,6 +60,7 @@ func SetApiRouter(router *gin.Engine) {
selfRoute
.
DELETE
(
"/self"
,
controller
.
DeleteSelf
)
selfRoute
.
DELETE
(
"/self"
,
controller
.
DeleteSelf
)
selfRoute
.
GET
(
"/token"
,
controller
.
GenerateAccessToken
)
selfRoute
.
GET
(
"/token"
,
controller
.
GenerateAccessToken
)
selfRoute
.
GET
(
"/aff"
,
controller
.
GetAffCode
)
selfRoute
.
GET
(
"/aff"
,
controller
.
GetAffCode
)
selfRoute
.
GET
(
"/topup/info"
,
controller
.
GetTopUpInfo
)
selfRoute
.
POST
(
"/topup"
,
middleware
.
CriticalRateLimit
(),
controller
.
TopUp
)
selfRoute
.
POST
(
"/topup"
,
middleware
.
CriticalRateLimit
(),
controller
.
TopUp
)
selfRoute
.
POST
(
"/pay"
,
middleware
.
CriticalRateLimit
(),
controller
.
RequestEpay
)
selfRoute
.
POST
(
"/pay"
,
middleware
.
CriticalRateLimit
(),
controller
.
RequestEpay
)
selfRoute
.
POST
(
"/amount"
,
controller
.
RequestAmount
)
selfRoute
.
POST
(
"/amount"
,
controller
.
RequestAmount
)
...
...
service/epay.go
View file @
c47d9fb5
...
@@ -2,11 +2,12 @@ package service
...
@@ -2,11 +2,12 @@ package service
import
(
import
(
"one-api/setting"
"one-api/setting"
"one-api/setting/operation_setting"
)
)
func
GetCallbackAddress
()
string
{
func
GetCallbackAddress
()
string
{
if
setting
.
CustomCallbackAddress
==
""
{
if
operation_
setting
.
CustomCallbackAddress
==
""
{
return
setting
.
ServerAddress
return
setting
.
ServerAddress
}
}
return
setting
.
CustomCallbackAddress
return
operation_
setting
.
CustomCallbackAddress
}
}
setting/operation_setting/payment_setting.go
0 → 100644
View file @
c47d9fb5
package
operation_setting
import
"one-api/setting/config"
type
PaymentSetting
struct
{
AmountOptions
[]
int
`json:"amount_options"`
AmountDiscount
map
[
int
]
float64
`json:"amount_discount"`
// 充值金额对应的折扣,例如 100 元 0.9 表示 100 元充值享受 9 折优惠
}
// 默认配置
var
paymentSetting
=
PaymentSetting
{
AmountOptions
:
[]
int
{
10
,
20
,
50
,
100
,
200
,
500
},
AmountDiscount
:
map
[
int
]
float64
{},
}
func
init
()
{
// 注册到全局配置管理器
config
.
GlobalConfig
.
Register
(
"payment_setting"
,
&
paymentSetting
)
}
func
GetPaymentSetting
()
*
PaymentSetting
{
return
&
paymentSetting
}
setting/
payment
.go
→
setting/
operation_setting/payment_setting_old
.go
View file @
c47d9fb5
package
setting
/**
此文件为旧版支付设置文件,如需增加新的参数、变量等,请在 payment_setting.go 中添加
This file is the old version of the payment settings file. If you need to add new parameters, variables, etc., please add them in payment_setting.go
*/
import
"encoding/json"
package
operation_setting
import
(
"one-api/common"
)
var
PayAddress
=
""
var
PayAddress
=
""
var
CustomCallbackAddress
=
""
var
CustomCallbackAddress
=
""
...
@@ -21,15 +28,21 @@ var PayMethods = []map[string]string{
...
@@ -21,15 +28,21 @@ var PayMethods = []map[string]string{
"color"
:
"rgba(var(--semi-green-5), 1)"
,
"color"
:
"rgba(var(--semi-green-5), 1)"
,
"type"
:
"wxpay"
,
"type"
:
"wxpay"
,
},
},
{
"name"
:
"自定义1"
,
"color"
:
"black"
,
"type"
:
"custom1"
,
"min_topup"
:
"50"
,
},
}
}
func
UpdatePayMethodsByJsonString
(
jsonString
string
)
error
{
func
UpdatePayMethodsByJsonString
(
jsonString
string
)
error
{
PayMethods
=
make
([]
map
[
string
]
string
,
0
)
PayMethods
=
make
([]
map
[
string
]
string
,
0
)
return
js
on
.
Unmarshal
([]
byte
(
jsonString
),
&
PayMethods
)
return
comm
on
.
Unmarshal
([]
byte
(
jsonString
),
&
PayMethods
)
}
}
func
PayMethods2JsonString
()
string
{
func
PayMethods2JsonString
()
string
{
jsonBytes
,
err
:=
js
on
.
Marshal
(
PayMethods
)
jsonBytes
,
err
:=
comm
on
.
Marshal
(
PayMethods
)
if
err
!=
nil
{
if
err
!=
nil
{
return
"[]"
return
"[]"
}
}
...
...
web/src/components/settings/PaymentSetting.jsx
View file @
c47d9fb5
...
@@ -37,6 +37,8 @@ const PaymentSetting = () => {
...
@@ -37,6 +37,8 @@ const PaymentSetting = () => {
TopupGroupRatio
:
''
,
TopupGroupRatio
:
''
,
CustomCallbackAddress
:
''
,
CustomCallbackAddress
:
''
,
PayMethods
:
''
,
PayMethods
:
''
,
AmountOptions
:
''
,
AmountDiscount
:
''
,
StripeApiSecret
:
''
,
StripeApiSecret
:
''
,
StripeWebhookSecret
:
''
,
StripeWebhookSecret
:
''
,
...
@@ -66,6 +68,30 @@ const PaymentSetting = () => {
...
@@ -66,6 +68,30 @@ const PaymentSetting = () => {
newInputs
[
item
.
key
]
=
item
.
value
;
newInputs
[
item
.
key
]
=
item
.
value
;
}
}
break
;
break
;
case
'payment_setting.amount_options'
:
try
{
newInputs
[
'AmountOptions'
]
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
,
);
}
catch
(
error
)
{
console
.
error
(
'解析AmountOptions出错:'
,
error
);
newInputs
[
'AmountOptions'
]
=
item
.
value
;
}
break
;
case
'payment_setting.amount_discount'
:
try
{
newInputs
[
'AmountDiscount'
]
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
,
);
}
catch
(
error
)
{
console
.
error
(
'解析AmountDiscount出错:'
,
error
);
newInputs
[
'AmountDiscount'
]
=
item
.
value
;
}
break
;
case
'Price'
:
case
'Price'
:
case
'MinTopUp'
:
case
'MinTopUp'
:
case
'StripeUnitPrice'
:
case
'StripeUnitPrice'
:
...
...
web/src/components/table/channels/modals/EditChannelModal.jsx
View file @
c47d9fb5
...
@@ -142,6 +142,8 @@ const EditChannelModal = (props) => {
...
@@ -142,6 +142,8 @@ const EditChannelModal = (props) => {
system_prompt
:
''
,
system_prompt
:
''
,
system_prompt_override
:
false
,
system_prompt_override
:
false
,
settings
:
''
,
settings
:
''
,
// 仅 Vertex: 密钥格式(存入 settings.vertex_key_type)
vertex_key_type
:
'json'
,
};
};
const
[
batch
,
setBatch
]
=
useState
(
false
);
const
[
batch
,
setBatch
]
=
useState
(
false
);
const
[
multiToSingle
,
setMultiToSingle
]
=
useState
(
false
);
const
[
multiToSingle
,
setMultiToSingle
]
=
useState
(
false
);
...
@@ -409,11 +411,17 @@ const EditChannelModal = (props) => {
...
@@ -409,11 +411,17 @@ const EditChannelModal = (props) => {
const
parsedSettings
=
JSON
.
parse
(
data
.
settings
);
const
parsedSettings
=
JSON
.
parse
(
data
.
settings
);
data
.
azure_responses_version
=
data
.
azure_responses_version
=
parsedSettings
.
azure_responses_version
||
''
;
parsedSettings
.
azure_responses_version
||
''
;
// 读取 Vertex 密钥格式
data
.
vertex_key_type
=
parsedSettings
.
vertex_key_type
||
'json'
;
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
'解析其他设置失败:'
,
error
);
console
.
error
(
'解析其他设置失败:'
,
error
);
data
.
azure_responses_version
=
''
;
data
.
azure_responses_version
=
''
;
data
.
region
=
''
;
data
.
region
=
''
;
data
.
vertex_key_type
=
'json'
;
}
}
}
else
{
// 兼容历史数据:老渠道没有 settings 时,默认按 json 展示
data
.
vertex_key_type
=
'json'
;
}
}
setInputs
(
data
);
setInputs
(
data
);
...
@@ -745,13 +753,19 @@ const EditChannelModal = (props) => {
...
@@ -745,13 +753,19 @@ const EditChannelModal = (props) => {
let
localInputs
=
{
...
formValues
};
let
localInputs
=
{
...
formValues
};
if
(
localInputs
.
type
===
41
)
{
if
(
localInputs
.
type
===
41
)
{
const
keyType
=
localInputs
.
vertex_key_type
||
'json'
;
if
(
keyType
===
'api_key'
)
{
// 直接作为普通字符串密钥处理
if
(
!
isEdit
&&
(
!
localInputs
.
key
||
localInputs
.
key
.
trim
()
===
''
))
{
showInfo
(
t
(
'请输入密钥!'
));
return
;
}
}
else
{
// JSON 服务账号密钥
if
(
useManualInput
)
{
if
(
useManualInput
)
{
// 手动输入模式
if
(
localInputs
.
key
&&
localInputs
.
key
.
trim
()
!==
''
)
{
if
(
localInputs
.
key
&&
localInputs
.
key
.
trim
()
!==
''
)
{
try
{
try
{
// 验证 JSON 格式
const
parsedKey
=
JSON
.
parse
(
localInputs
.
key
);
const
parsedKey
=
JSON
.
parse
(
localInputs
.
key
);
// 确保是有效的密钥格式
localInputs
.
key
=
JSON
.
stringify
(
parsedKey
);
localInputs
.
key
=
JSON
.
stringify
(
parsedKey
);
}
catch
(
err
)
{
}
catch
(
err
)
{
showError
(
t
(
'密钥格式无效,请输入有效的 JSON 格式密钥'
));
showError
(
t
(
'密钥格式无效,请输入有效的 JSON 格式密钥'
));
...
@@ -764,8 +778,6 @@ const EditChannelModal = (props) => {
...
@@ -764,8 +778,6 @@ const EditChannelModal = (props) => {
}
else
{
}
else
{
// 文件上传模式
// 文件上传模式
let
keys
=
vertexKeys
;
let
keys
=
vertexKeys
;
// 若当前未选择文件,尝试从已上传文件列表解析(异步读取)
if
(
keys
.
length
===
0
&&
vertexFileList
.
length
>
0
)
{
if
(
keys
.
length
===
0
&&
vertexFileList
.
length
>
0
)
{
try
{
try
{
const
parsed
=
await
Promise
.
all
(
const
parsed
=
await
Promise
.
all
(
...
@@ -782,22 +794,15 @@ const EditChannelModal = (props) => {
...
@@ -782,22 +794,15 @@ const EditChannelModal = (props) => {
return
;
return
;
}
}
}
}
// 创建模式必须上传密钥;编辑模式可选
if
(
keys
.
length
===
0
)
{
if
(
keys
.
length
===
0
)
{
if
(
!
isEdit
)
{
if
(
!
isEdit
)
{
showInfo
(
t
(
'请上传密钥文件!'
));
showInfo
(
t
(
'请上传密钥文件!'
));
return
;
return
;
}
else
{
}
else
{
// 编辑模式且未上传新密钥,不修改 key
delete
localInputs
.
key
;
delete
localInputs
.
key
;
}
}
}
else
{
}
else
{
// 有新密钥,则覆盖
localInputs
.
key
=
batch
?
JSON
.
stringify
(
keys
)
:
JSON
.
stringify
(
keys
[
0
]);
if
(
batch
)
{
localInputs
.
key
=
JSON
.
stringify
(
keys
);
}
else
{
localInputs
.
key
=
JSON
.
stringify
(
keys
[
0
]);
}
}
}
}
}
}
...
@@ -853,6 +858,8 @@ const EditChannelModal = (props) => {
...
@@ -853,6 +858,8 @@ const EditChannelModal = (props) => {
delete
localInputs
.
pass_through_body_enabled
;
delete
localInputs
.
pass_through_body_enabled
;
delete
localInputs
.
system_prompt
;
delete
localInputs
.
system_prompt
;
delete
localInputs
.
system_prompt_override
;
delete
localInputs
.
system_prompt_override
;
// 顶层的 vertex_key_type 不应发送给后端
delete
localInputs
.
vertex_key_type
;
let
res
;
let
res
;
localInputs
.
auto_ban
=
localInputs
.
auto_ban
?
1
:
0
;
localInputs
.
auto_ban
=
localInputs
.
auto_ban
?
1
:
0
;
...
@@ -1178,8 +1185,40 @@ const EditChannelModal = (props) => {
...
@@ -1178,8 +1185,40 @@ const EditChannelModal = (props) => {
autoComplete=
'new-password'
autoComplete=
'new-password'
/>
/>
{
inputs
.
type
===
41
&&
(
<
Form
.
Select
field=
'vertex_key_type'
label=
{
t
(
'密钥格式'
)
}
placeholder=
{
t
(
'请选择密钥格式'
)
}
optionList=
{
[
{
label
:
'JSON'
,
value
:
'json'
},
{
label
:
'API Key'
,
value
:
'api_key'
},
]
}
style=
{
{
width
:
'100%'
}
}
value=
{
inputs
.
vertex_key_type
||
'json'
}
onChange=
{
(
value
)
=>
{
// 更新设置中的 vertex_key_type
handleChannelOtherSettingsChange
(
'vertex_key_type'
,
value
);
// 切换为 api_key 时,关闭批量与手动/文件切换,并清理已选文件
if
(
value
===
'api_key'
)
{
setBatch
(
false
);
setUseManualInput
(
false
);
setVertexKeys
([]);
setVertexFileList
([]);
if
(
formApiRef
.
current
)
{
formApiRef
.
current
.
setValue
(
'vertex_files'
,
[]);
}
}
}
}
extraText=
{
inputs
.
vertex_key_type
===
'api_key'
?
t
(
'API Key 模式下不支持批量创建'
)
:
t
(
'JSON 模式支持手动输入或上传服务账号 JSON'
)
}
/>
)
}
{
batch
?
(
{
batch
?
(
inputs
.
type
===
41
?
(
inputs
.
type
===
41
&&
(
inputs
.
vertex_key_type
||
'json'
)
===
'json'
?
(
<
Form
.
Upload
<
Form
.
Upload
field=
'vertex_files'
field=
'vertex_files'
label=
{
t
(
'密钥文件 (.json)'
)
}
label=
{
t
(
'密钥文件 (.json)'
)
}
...
@@ -1243,7 +1282,7 @@ const EditChannelModal = (props) => {
...
@@ -1243,7 +1282,7 @@ const EditChannelModal = (props) => {
)
)
)
:
(
)
:
(
<>
<>
{
inputs
.
type
===
41
?
(
{
inputs
.
type
===
41
&&
(
inputs
.
vertex_key_type
||
'json'
)
===
'json'
?
(
<>
<>
{
!
batch
&&
(
{
!
batch
&&
(
<
div
className=
'flex items-center justify-between mb-3'
>
<
div
className=
'flex items-center justify-between mb-3'
>
...
...
web/src/components/topup/RechargeCard.jsx
View file @
c47d9fb5
...
@@ -21,6 +21,7 @@ import React, { useRef } from 'react';
...
@@ -21,6 +21,7 @@ import React, { useRef } from 'react';
import
{
import
{
Avatar
,
Avatar
,
Typography
,
Typography
,
Tag
,
Card
,
Card
,
Button
,
Button
,
Banner
,
Banner
,
...
@@ -29,7 +30,7 @@ import {
...
@@ -29,7 +30,7 @@ import {
Space
,
Space
,
Row
,
Row
,
Col
,
Col
,
Spin
,
Spin
,
Tooltip
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
SiAlipay
,
SiWechat
,
SiStripe
}
from
'react-icons/si'
;
import
{
SiAlipay
,
SiWechat
,
SiStripe
}
from
'react-icons/si'
;
import
{
CreditCard
,
Coins
,
Wallet
,
BarChart2
,
TrendingUp
}
from
'lucide-react'
;
import
{
CreditCard
,
Coins
,
Wallet
,
BarChart2
,
TrendingUp
}
from
'lucide-react'
;
...
@@ -68,6 +69,7 @@ const RechargeCard = ({
...
@@ -68,6 +69,7 @@ const RechargeCard = ({
userState
,
userState
,
renderQuota
,
renderQuota
,
statusLoading
,
statusLoading
,
topupInfo
,
})
=>
{
})
=>
{
const
onlineFormApiRef
=
useRef
(
null
);
const
onlineFormApiRef
=
useRef
(
null
);
const
redeemFormApiRef
=
useRef
(
null
);
const
redeemFormApiRef
=
useRef
(
null
);
...
@@ -261,22 +263,24 @@ const RechargeCard = ({
...
@@ -261,22 +263,24 @@ const RechargeCard = ({
</
Col
>
</
Col
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
24
}
lg=
{
14
}
xl=
{
14
}
>
<
Col
xs=
{
24
}
sm=
{
24
}
md=
{
24
}
lg=
{
14
}
xl=
{
14
}
>
<
Form
.
Slot
label=
{
t
(
'选择支付方式'
)
}
>
<
Form
.
Slot
label=
{
t
(
'选择支付方式'
)
}
>
{
payMethods
&&
payMethods
.
length
>
0
?
(
<
Space
wrap
>
<
Space
wrap
>
{
payMethods
.
map
((
payMethod
)
=>
(
{
payMethods
.
map
((
payMethod
)
=>
{
const
minTopupVal
=
Number
(
payMethod
.
min_topup
)
||
0
;
const
isStripe
=
payMethod
.
type
===
'stripe'
;
const
disabled
=
(
!
enableOnlineTopUp
&&
!
isStripe
)
||
(
!
enableStripeTopUp
&&
isStripe
)
||
minTopupVal
>
Number
(
topUpCount
||
0
);
const
buttonEl
=
(
<
Button
<
Button
key=
{
payMethod
.
type
}
key=
{
payMethod
.
type
}
theme=
'outline'
theme=
'outline'
type=
'tertiary'
type=
'tertiary'
onClick=
{
()
=>
preTopUp
(
payMethod
.
type
)
}
onClick=
{
()
=>
preTopUp
(
payMethod
.
type
)
}
disabled=
{
disabled=
{
disabled
}
(
!
enableOnlineTopUp
&&
loading=
{
paymentLoading
&&
payWay
===
payMethod
.
type
}
payMethod
.
type
!==
'stripe'
)
||
(
!
enableStripeTopUp
&&
payMethod
.
type
===
'stripe'
)
}
loading=
{
paymentLoading
&&
payWay
===
payMethod
.
type
}
icon=
{
icon=
{
payMethod
.
type
===
'alipay'
?
(
payMethod
.
type
===
'alipay'
?
(
<
SiAlipay
size=
{
18
}
color=
'#1677FF'
/>
<
SiAlipay
size=
{
18
}
color=
'#1677FF'
/>
...
@@ -287,18 +291,30 @@ const RechargeCard = ({
...
@@ -287,18 +291,30 @@ const RechargeCard = ({
)
:
(
)
:
(
<
CreditCard
<
CreditCard
size=
{
18
}
size=
{
18
}
color=
{
color=
{
payMethod
.
color
||
'var(--semi-color-text-2)'
}
payMethod
.
color
||
'var(--semi-color-text-2)'
}
/>
/>
)
)
}
}
className=
'!rounded-lg !px-4 !py-2'
>
>
{
payMethod
.
name
}
{
payMethod
.
name
}
</
Button
>
</
Button
>
))
}
);
return
disabled
&&
minTopupVal
>
Number
(
topUpCount
||
0
)
?
(
<
Tooltip
content=
{
t
(
'此支付方式最低充值金额为'
)
+
' '
+
minTopupVal
}
key=
{
payMethod
.
type
}
>
{
buttonEl
}
</
Tooltip
>
)
:
(
<
React
.
Fragment
key=
{
payMethod
.
type
}
>
{
buttonEl
}
</
React
.
Fragment
>
);
})
}
</
Space
>
</
Space
>
)
:
(
<
div
className=
'text-gray-500 text-sm p-3 bg-gray-50 rounded-lg border border-dashed border-gray-300'
>
{
t
(
'暂无可用的支付方式,请联系管理员配置'
)
}
</
div
>
)
}
</
Form
.
Slot
>
</
Form
.
Slot
>
</
Col
>
</
Col
>
</
Row
>
</
Row
>
...
@@ -306,20 +322,27 @@ const RechargeCard = ({
...
@@ -306,20 +322,27 @@ const RechargeCard = ({
{
(
enableOnlineTopUp
||
enableStripeTopUp
)
&&
(
{
(
enableOnlineTopUp
||
enableStripeTopUp
)
&&
(
<
Form
.
Slot
label=
{
t
(
'选择充值额度'
)
}
>
<
Form
.
Slot
label=
{
t
(
'选择充值额度'
)
}
>
<
Space
wrap
>
<
div
className=
'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2'
>
{
presetAmounts
.
map
((
preset
,
index
)
=>
(
{
presetAmounts
.
map
((
preset
,
index
)
=>
{
<
Button
const
discount
=
preset
.
discount
||
topupInfo
?.
discount
?.[
preset
.
value
]
||
1.0
;
const
originalPrice
=
preset
.
value
*
priceRatio
;
const
discountedPrice
=
originalPrice
*
discount
;
const
hasDiscount
=
discount
<
1.0
;
const
actualPay
=
discountedPrice
;
const
save
=
originalPrice
-
discountedPrice
;
return
(
<
Card
key=
{
index
}
key=
{
index
}
theme=
{
style=
{
{
selectedPreset
===
preset
.
value
cursor
:
'pointer'
,
?
'solid'
border
:
selectedPreset
===
preset
.
value
:
'outline'
?
'2px solid var(--semi-color-primary)'
}
:
'1px solid var(--semi-color-border)'
,
type=
{
height
:
'100%'
,
selectedPreset
===
preset
.
value
width
:
'100%'
?
'primary'
}
}
:
'tertiary'
bodyStyle=
{
{
padding
:
'12px'
}
}
}
onClick=
{
()
=>
{
onClick=
{
()
=>
{
selectPresetAmount
(
preset
);
selectPresetAmount
(
preset
);
onlineFormApiRef
.
current
?.
setValue
(
onlineFormApiRef
.
current
?.
setValue
(
...
@@ -327,20 +350,31 @@ const RechargeCard = ({
...
@@ -327,20 +350,31 @@ const RechargeCard = ({
preset
.
value
,
preset
.
value
,
);
);
}
}
}
}
className=
'!rounded-lg !py-2 !px-3'
>
>
<
div
className=
'flex items-center gap-2'
>
<
div
style=
{
{
textAlign
:
'center'
}
}
>
<
Coins
size=
{
14
}
className=
'opacity-80'
/>
<
Typography
.
Title
heading=
{
6
}
style=
{
{
margin
:
'0 0 8px 0'
}
}
>
<
span
className=
'font-medium'
>
{
formatLargeNumber
(
preset
.
value
)
}
{
t
(
'美元额度'
)
}
{
formatLargeNumber
(
preset
.
value
)
}
{
hasDiscount
&&
(
</
span
>
<
Tag
style=
{
{
marginLeft
:
4
}
}
color=
"green"
>
<
span
className=
'text-xs text-gray-500'
>
{
t
(
'折'
).
includes
(
'off'
)
?
¥
{
(
preset
.
value
*
priceRatio
).
toFixed
(
2
)
}
((
1
-
discount
)
*
100
).
toFixed
(
1
)
:
</
span
>
(
discount
*
10
).
toFixed
(
1
)
}{
t
(
'折'
)
}
</
Tag
>
)
}
</
Typography
.
Title
>
<
div
style=
{
{
color
:
'var(--semi-color-text-2)'
,
fontSize
:
'12px'
,
margin
:
'4px 0'
}
}
>
{
t
(
'实付'
)
}
{
actualPay
.
toFixed
(
2
)
}
,
{
hasDiscount
?
`${t('节省')} ${save.toFixed(2)}`
:
`${t('节省')} 0.00`
}
</
div
>
</
div
>
</
Card
>
);
})
}
</
div
>
</
div
>
</
Button
>
))
}
</
Space
>
</
Form
.
Slot
>
</
Form
.
Slot
>
)
}
)
}
</
div
>
</
div
>
...
...
web/src/components/topup/index.jsx
View file @
c47d9fb5
...
@@ -81,6 +81,12 @@ const TopUp = () => {
...
@@ -81,6 +81,12 @@ const TopUp = () => {
const
[
presetAmounts
,
setPresetAmounts
]
=
useState
([]);
const
[
presetAmounts
,
setPresetAmounts
]
=
useState
([]);
const
[
selectedPreset
,
setSelectedPreset
]
=
useState
(
null
);
const
[
selectedPreset
,
setSelectedPreset
]
=
useState
(
null
);
// 充值配置信息
const
[
topupInfo
,
setTopupInfo
]
=
useState
({
amount_options
:
[],
discount
:
{}
});
const
topUp
=
async
()
=>
{
const
topUp
=
async
()
=>
{
if
(
redemptionCode
===
''
)
{
if
(
redemptionCode
===
''
)
{
showInfo
(
t
(
'请输入兑换码!'
));
showInfo
(
t
(
'请输入兑换码!'
));
...
@@ -248,6 +254,99 @@ const TopUp = () => {
...
@@ -248,6 +254,99 @@ const TopUp = () => {
}
}
};
};
// 获取充值配置信息
const
getTopupInfo
=
async
()
=>
{
try
{
const
res
=
await
API
.
get
(
'/api/user/topup/info'
);
const
{
message
,
data
,
success
}
=
res
.
data
;
if
(
success
)
{
setTopupInfo
({
amount_options
:
data
.
amount_options
||
[],
discount
:
data
.
discount
||
{}
});
// 处理支付方式
let
payMethods
=
data
.
pay_methods
||
[];
try
{
if
(
typeof
payMethods
===
'string'
)
{
payMethods
=
JSON
.
parse
(
payMethods
);
}
if
(
payMethods
&&
payMethods
.
length
>
0
)
{
// 检查name和type是否为空
payMethods
=
payMethods
.
filter
((
method
)
=>
{
return
method
.
name
&&
method
.
type
;
});
// 如果没有color,则设置默认颜色
payMethods
=
payMethods
.
map
((
method
)
=>
{
// 规范化最小充值数
const
normalizedMinTopup
=
Number
(
method
.
min_topup
);
method
.
min_topup
=
Number
.
isFinite
(
normalizedMinTopup
)
?
normalizedMinTopup
:
0
;
// Stripe 的最小充值从后端字段回填
if
(
method
.
type
===
'stripe'
&&
(
!
method
.
min_topup
||
method
.
min_topup
<=
0
))
{
const
stripeMin
=
Number
(
data
.
stripe_min_topup
);
if
(
Number
.
isFinite
(
stripeMin
))
{
method
.
min_topup
=
stripeMin
;
}
}
if
(
!
method
.
color
)
{
if
(
method
.
type
===
'alipay'
)
{
method
.
color
=
'rgba(var(--semi-blue-5), 1)'
;
}
else
if
(
method
.
type
===
'wxpay'
)
{
method
.
color
=
'rgba(var(--semi-green-5), 1)'
;
}
else
if
(
method
.
type
===
'stripe'
)
{
method
.
color
=
'rgba(var(--semi-purple-5), 1)'
;
}
else
{
method
.
color
=
'rgba(var(--semi-primary-5), 1)'
;
}
}
return
method
;
});
}
else
{
payMethods
=
[];
}
// 如果启用了 Stripe 支付,添加到支付方法列表
// 这个逻辑现在由后端处理,如果 Stripe 启用,后端会在 pay_methods 中包含它
setPayMethods
(
payMethods
);
const
enableStripeTopUp
=
data
.
enable_stripe_topup
||
false
;
const
enableOnlineTopUp
=
data
.
enable_online_topup
||
false
;
const
minTopUpValue
=
enableOnlineTopUp
?
data
.
min_topup
:
enableStripeTopUp
?
data
.
stripe_min_topup
:
1
;
setEnableOnlineTopUp
(
enableOnlineTopUp
);
setEnableStripeTopUp
(
enableStripeTopUp
);
setMinTopUp
(
minTopUpValue
);
setTopUpCount
(
minTopUpValue
);
// 如果没有自定义充值数量选项,根据最小充值金额生成预设充值额度选项
if
(
topupInfo
.
amount_options
.
length
===
0
)
{
setPresetAmounts
(
generatePresetAmounts
(
minTopUpValue
));
}
// 初始化显示实付金额
getAmount
(
minTopUpValue
);
}
catch
(
e
)
{
console
.
log
(
'解析支付方式失败:'
,
e
);
setPayMethods
([]);
}
// 如果有自定义充值数量选项,使用它们替换默认的预设选项
if
(
data
.
amount_options
&&
data
.
amount_options
.
length
>
0
)
{
const
customPresets
=
data
.
amount_options
.
map
(
amount
=>
({
value
:
amount
,
discount
:
data
.
discount
[
amount
]
||
1.0
}));
setPresetAmounts
(
customPresets
);
}
}
else
{
console
.
error
(
'获取充值配置失败:'
,
data
);
}
}
catch
(
error
)
{
console
.
error
(
'获取充值配置异常:'
,
error
);
}
};
// 获取邀请链接
// 获取邀请链接
const
getAffLink
=
async
()
=>
{
const
getAffLink
=
async
()
=>
{
const
res
=
await
API
.
get
(
'/api/user/aff'
);
const
res
=
await
API
.
get
(
'/api/user/aff'
);
...
@@ -290,52 +389,7 @@ const TopUp = () => {
...
@@ -290,52 +389,7 @@ const TopUp = () => {
getUserQuota
().
then
();
getUserQuota
().
then
();
}
}
setTransferAmount
(
getQuotaPerUnit
());
setTransferAmount
(
getQuotaPerUnit
());
},
[]);
let
payMethods
=
localStorage
.
getItem
(
'pay_methods'
);
try
{
payMethods
=
JSON
.
parse
(
payMethods
);
if
(
payMethods
&&
payMethods
.
length
>
0
)
{
// 检查name和type是否为空
payMethods
=
payMethods
.
filter
((
method
)
=>
{
return
method
.
name
&&
method
.
type
;
});
// 如果没有color,则设置默认颜色
payMethods
=
payMethods
.
map
((
method
)
=>
{
if
(
!
method
.
color
)
{
if
(
method
.
type
===
'alipay'
)
{
method
.
color
=
'rgba(var(--semi-blue-5), 1)'
;
}
else
if
(
method
.
type
===
'wxpay'
)
{
method
.
color
=
'rgba(var(--semi-green-5), 1)'
;
}
else
if
(
method
.
type
===
'stripe'
)
{
method
.
color
=
'rgba(var(--semi-purple-5), 1)'
;
}
else
{
method
.
color
=
'rgba(var(--semi-primary-5), 1)'
;
}
}
return
method
;
});
}
else
{
payMethods
=
[];
}
// 如果启用了 Stripe 支付,添加到支付方法列表
if
(
statusState
?.
status
?.
enable_stripe_topup
)
{
const
hasStripe
=
payMethods
.
some
((
method
)
=>
method
.
type
===
'stripe'
);
if
(
!
hasStripe
)
{
payMethods
.
push
({
name
:
'Stripe'
,
type
:
'stripe'
,
color
:
'rgba(var(--semi-purple-5), 1)'
,
});
}
}
setPayMethods
(
payMethods
);
}
catch
(
e
)
{
console
.
log
(
e
);
showError
(
t
(
'支付方式配置错误, 请联系管理员'
));
}
},
[
statusState
?.
status
?.
enable_stripe_topup
]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
affFetchedRef
.
current
)
return
;
if
(
affFetchedRef
.
current
)
return
;
...
@@ -343,20 +397,18 @@ const TopUp = () => {
...
@@ -343,20 +397,18 @@ const TopUp = () => {
getAffLink
().
then
();
getAffLink
().
then
();
},
[]);
},
[]);
// 在 statusState 可用时获取充值信息
useEffect
(()
=>
{
getTopupInfo
().
then
();
},
[]);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
statusState
?.
status
)
{
if
(
statusState
?.
status
)
{
const
minTopUpValue
=
statusState
.
status
.
min_topup
||
1
;
//
const minTopUpValue = statusState.status.min_topup || 1;
setMinTopUp
(
minTopUpValue
);
//
setMinTopUp(minTopUpValue);
setTopUpCount
(
minTopUpValue
);
//
setTopUpCount(minTopUpValue);
setTopUpLink
(
statusState
.
status
.
top_up_link
||
''
);
setTopUpLink
(
statusState
.
status
.
top_up_link
||
''
);
setEnableOnlineTopUp
(
statusState
.
status
.
enable_online_topup
||
false
);
setPriceRatio
(
statusState
.
status
.
price
||
1
);
setPriceRatio
(
statusState
.
status
.
price
||
1
);
setEnableStripeTopUp
(
statusState
.
status
.
enable_stripe_topup
||
false
);
// 根据最小充值金额生成预设充值额度选项
setPresetAmounts
(
generatePresetAmounts
(
minTopUpValue
));
// 初始化显示实付金额
getAmount
(
minTopUpValue
);
setStatusLoading
(
false
);
setStatusLoading
(
false
);
}
}
...
@@ -431,7 +483,11 @@ const TopUp = () => {
...
@@ -431,7 +483,11 @@ const TopUp = () => {
const
selectPresetAmount
=
(
preset
)
=>
{
const
selectPresetAmount
=
(
preset
)
=>
{
setTopUpCount
(
preset
.
value
);
setTopUpCount
(
preset
.
value
);
setSelectedPreset
(
preset
.
value
);
setSelectedPreset
(
preset
.
value
);
setAmount
(
preset
.
value
*
priceRatio
);
// 计算实际支付金额,考虑折扣
const
discount
=
preset
.
discount
||
topupInfo
.
discount
[
preset
.
value
]
||
1.0
;
const
discountedAmount
=
preset
.
value
*
priceRatio
*
discount
;
setAmount
(
discountedAmount
);
};
};
// 格式化大数字显示
// 格式化大数字显示
...
@@ -475,6 +531,8 @@ const TopUp = () => {
...
@@ -475,6 +531,8 @@ const TopUp = () => {
renderAmount=
{
renderAmount
}
renderAmount=
{
renderAmount
}
payWay=
{
payWay
}
payWay=
{
payWay
}
payMethods=
{
payMethods
}
payMethods=
{
payMethods
}
amountNumber=
{
amount
}
discountRate=
{
topupInfo
?.
discount
?.[
topUpCount
]
||
1.0
}
/>
/>
{
/* 用户信息头部 */
}
{
/* 用户信息头部 */
}
...
@@ -512,6 +570,7 @@ const TopUp = () => {
...
@@ -512,6 +570,7 @@ const TopUp = () => {
userState=
{
userState
}
userState=
{
userState
}
renderQuota=
{
renderQuota
}
renderQuota=
{
renderQuota
}
statusLoading=
{
statusLoading
}
statusLoading=
{
statusLoading
}
topupInfo=
{
topupInfo
}
/>
/>
</
div
>
</
div
>
...
...
web/src/components/topup/modals/PaymentConfirmModal.jsx
View file @
c47d9fb5
...
@@ -36,7 +36,13 @@ const PaymentConfirmModal = ({
...
@@ -36,7 +36,13 @@ const PaymentConfirmModal = ({
renderAmount
,
renderAmount
,
payWay
,
payWay
,
payMethods
,
payMethods
,
// 新增:用于显示折扣明细
amountNumber
,
discountRate
,
})
=>
{
})
=>
{
const
hasDiscount
=
discountRate
&&
discountRate
>
0
&&
discountRate
<
1
&&
amountNumber
>
0
;
const
originalAmount
=
hasDiscount
?
(
amountNumber
/
discountRate
)
:
0
;
const
discountAmount
=
hasDiscount
?
(
originalAmount
-
amountNumber
)
:
0
;
return
(
return
(
<
Modal
<
Modal
title=
{
title=
{
...
@@ -71,11 +77,38 @@ const PaymentConfirmModal = ({
...
@@ -71,11 +77,38 @@ const PaymentConfirmModal = ({
{
amountLoading
?
(
{
amountLoading
?
(
<
Skeleton
.
Title
style=
{
{
width
:
'60px'
,
height
:
'16px'
}
}
/>
<
Skeleton
.
Title
style=
{
{
width
:
'60px'
,
height
:
'16px'
}
}
/>
)
:
(
)
:
(
<
div
className=
'flex items-baseline space-x-2'
>
<
Text
strong
className=
'font-bold'
style=
{
{
color
:
'red'
}
}
>
<
Text
strong
className=
'font-bold'
style=
{
{
color
:
'red'
}
}
>
{
renderAmount
()
}
{
renderAmount
()
}
</
Text
>
</
Text
>
{
hasDiscount
&&
(
<
Text
size=
'small'
className=
'text-rose-500'
>
{
Math
.
round
(
discountRate
*
100
)
}
%
</
Text
>
)
}
</
div
>
)
}
)
}
</
div
>
</
div
>
{
hasDiscount
&&
!
amountLoading
&&
(
<>
<
div
className=
'flex justify-between items-center'
>
<
Text
className=
'text-slate-500 dark:text-slate-400'
>
{
t
(
'原价'
)
}
:
</
Text
>
<
Text
delete
className=
'text-slate-500 dark:text-slate-400'
>
{
`${originalAmount.toFixed(2)} ${t('元')}`
}
</
Text
>
</
div
>
<
div
className=
'flex justify-between items-center'
>
<
Text
className=
'text-slate-500 dark:text-slate-400'
>
{
t
(
'优惠'
)
}
:
</
Text
>
<
Text
className=
'text-emerald-600 dark:text-emerald-400'
>
{
`- ${discountAmount.toFixed(2)} ${t('元')}`
}
</
Text
>
</
div
>
</>
)
}
<
div
className=
'flex justify-between items-center'
>
<
div
className=
'flex justify-between items-center'
>
<
Text
strong
className=
'text-slate-700 dark:text-slate-200'
>
<
Text
strong
className=
'text-slate-700 dark:text-slate-200'
>
{
t
(
'支付方式'
)
}
:
{
t
(
'支付方式'
)
}
:
...
...
web/src/helpers/data.js
View file @
c47d9fb5
...
@@ -28,7 +28,6 @@ export function setStatusData(data) {
...
@@ -28,7 +28,6 @@ export function setStatusData(data) {
localStorage
.
setItem
(
'enable_task'
,
data
.
enable_task
);
localStorage
.
setItem
(
'enable_task'
,
data
.
enable_task
);
localStorage
.
setItem
(
'enable_data_export'
,
data
.
enable_data_export
);
localStorage
.
setItem
(
'enable_data_export'
,
data
.
enable_data_export
);
localStorage
.
setItem
(
'chats'
,
JSON
.
stringify
(
data
.
chats
));
localStorage
.
setItem
(
'chats'
,
JSON
.
stringify
(
data
.
chats
));
localStorage
.
setItem
(
'pay_methods'
,
JSON
.
stringify
(
data
.
pay_methods
));
localStorage
.
setItem
(
localStorage
.
setItem
(
'data_export_default_time'
,
'data_export_default_time'
,
data
.
data_export_default_time
,
data
.
data_export_default_time
,
...
...
web/src/pages/Setting/Payment/SettingsPaymentGateway.jsx
View file @
c47d9fb5
...
@@ -41,6 +41,8 @@ export default function SettingsPaymentGateway(props) {
...
@@ -41,6 +41,8 @@ export default function SettingsPaymentGateway(props) {
TopupGroupRatio
:
''
,
TopupGroupRatio
:
''
,
CustomCallbackAddress
:
''
,
CustomCallbackAddress
:
''
,
PayMethods
:
''
,
PayMethods
:
''
,
AmountOptions
:
''
,
AmountDiscount
:
''
,
});
});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
formApiRef
=
useRef
(
null
);
const
formApiRef
=
useRef
(
null
);
...
@@ -62,7 +64,30 @@ export default function SettingsPaymentGateway(props) {
...
@@ -62,7 +64,30 @@ export default function SettingsPaymentGateway(props) {
TopupGroupRatio
:
props
.
options
.
TopupGroupRatio
||
''
,
TopupGroupRatio
:
props
.
options
.
TopupGroupRatio
||
''
,
CustomCallbackAddress
:
props
.
options
.
CustomCallbackAddress
||
''
,
CustomCallbackAddress
:
props
.
options
.
CustomCallbackAddress
||
''
,
PayMethods
:
props
.
options
.
PayMethods
||
''
,
PayMethods
:
props
.
options
.
PayMethods
||
''
,
AmountOptions
:
props
.
options
.
AmountOptions
||
''
,
AmountDiscount
:
props
.
options
.
AmountDiscount
||
''
,
};
};
// 美化 JSON 展示
try
{
if
(
currentInputs
.
AmountOptions
)
{
currentInputs
.
AmountOptions
=
JSON
.
stringify
(
JSON
.
parse
(
currentInputs
.
AmountOptions
),
null
,
2
,
);
}
}
catch
{}
try
{
if
(
currentInputs
.
AmountDiscount
)
{
currentInputs
.
AmountDiscount
=
JSON
.
stringify
(
JSON
.
parse
(
currentInputs
.
AmountDiscount
),
null
,
2
,
);
}
}
catch
{}
setInputs
(
currentInputs
);
setInputs
(
currentInputs
);
setOriginInputs
({
...
currentInputs
});
setOriginInputs
({
...
currentInputs
});
formApiRef
.
current
.
setValues
(
currentInputs
);
formApiRef
.
current
.
setValues
(
currentInputs
);
...
@@ -93,6 +118,20 @@ export default function SettingsPaymentGateway(props) {
...
@@ -93,6 +118,20 @@ export default function SettingsPaymentGateway(props) {
}
}
}
}
if
(
originInputs
[
'AmountOptions'
]
!==
inputs
.
AmountOptions
&&
inputs
.
AmountOptions
.
trim
()
!==
''
)
{
if
(
!
verifyJSON
(
inputs
.
AmountOptions
))
{
showError
(
t
(
'自定义充值数量选项不是合法的 JSON 数组'
));
return
;
}
}
if
(
originInputs
[
'AmountDiscount'
]
!==
inputs
.
AmountDiscount
&&
inputs
.
AmountDiscount
.
trim
()
!==
''
)
{
if
(
!
verifyJSON
(
inputs
.
AmountDiscount
))
{
showError
(
t
(
'充值金额折扣配置不是合法的 JSON 对象'
));
return
;
}
}
setLoading
(
true
);
setLoading
(
true
);
try
{
try
{
const
options
=
[
const
options
=
[
...
@@ -123,6 +162,12 @@ export default function SettingsPaymentGateway(props) {
...
@@ -123,6 +162,12 @@ export default function SettingsPaymentGateway(props) {
if
(
originInputs
[
'PayMethods'
]
!==
inputs
.
PayMethods
)
{
if
(
originInputs
[
'PayMethods'
]
!==
inputs
.
PayMethods
)
{
options
.
push
({
key
:
'PayMethods'
,
value
:
inputs
.
PayMethods
});
options
.
push
({
key
:
'PayMethods'
,
value
:
inputs
.
PayMethods
});
}
}
if
(
originInputs
[
'AmountOptions'
]
!==
inputs
.
AmountOptions
)
{
options
.
push
({
key
:
'payment_setting.amount_options'
,
value
:
inputs
.
AmountOptions
});
}
if
(
originInputs
[
'AmountDiscount'
]
!==
inputs
.
AmountDiscount
)
{
options
.
push
({
key
:
'payment_setting.amount_discount'
,
value
:
inputs
.
AmountDiscount
});
}
// 发送请求
// 发送请求
const
requestQueue
=
options
.
map
((
opt
)
=>
const
requestQueue
=
options
.
map
((
opt
)
=>
...
@@ -228,6 +273,37 @@ export default function SettingsPaymentGateway(props) {
...
@@ -228,6 +273,37 @@ export default function SettingsPaymentGateway(props) {
placeholder=
{
t
(
'为一个 JSON 文本'
)
}
placeholder=
{
t
(
'为一个 JSON 文本'
)
}
autosize
autosize
/>
/>
<
Row
gutter=
{
{
xs
:
8
,
sm
:
16
,
md
:
24
,
lg
:
24
,
xl
:
24
,
xxl
:
24
}
}
style=
{
{
marginTop
:
16
}
}
>
<
Col
span=
{
24
}
>
<
Form
.
TextArea
field=
'AmountOptions'
label=
{
t
(
'自定义充值数量选项'
)
}
placeholder=
{
t
(
'为一个 JSON 数组,例如:[10, 20, 50, 100, 200, 500]'
)
}
autosize
extraText=
{
t
(
'设置用户可选择的充值数量选项,例如:[10, 20, 50, 100, 200, 500]'
)
}
/>
</
Col
>
</
Row
>
<
Row
gutter=
{
{
xs
:
8
,
sm
:
16
,
md
:
24
,
lg
:
24
,
xl
:
24
,
xxl
:
24
}
}
style=
{
{
marginTop
:
16
}
}
>
<
Col
span=
{
24
}
>
<
Form
.
TextArea
field=
'AmountDiscount'
label=
{
t
(
'充值金额折扣配置'
)
}
placeholder=
{
t
(
'为一个
JSON
对象,例如:
{
"100"
:
0.95
,
"200"
:
0.9
,
"500"
:
0.85
}
'
)
}
autosize
extraText=
{
t
(
'设置不同充值金额对应的折扣,键为充值金额,值为折扣率,例如:
{
"100"
:
0.95
,
"200"
:
0.9
,
"500"
:
0.85
}
'
)
}
/>
</
Col
>
</
Row
>
<
Button
onClick=
{
submitPayAddress
}
>
{
t
(
'更新支付设置'
)
}
</
Button
>
<
Button
onClick=
{
submitPayAddress
}
>
{
t
(
'更新支付设置'
)
}
</
Button
>
</
Form
.
Section
>
</
Form
.
Section
>
</
Form
>
</
Form
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment