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
51a7aa44
authored
Sep 08, 2025
by
Little Write
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
完成 后端 部分,webo hhok 待完善
parent
6d81312e
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
323 additions
and
0 deletions
+323
-0
controller/misc.go
+2
-0
controller/topup_creem.go
+256
-0
model/option.go
+9
-0
model/topup.go
+49
-0
router/api-router.go
+2
-0
setting/payment_creem.go
+5
-0
No files found.
controller/misc.go
View file @
51a7aa44
...
...
@@ -74,6 +74,8 @@ func GetStatus(c *gin.Context) {
"default_collapse_sidebar"
:
common
.
DefaultCollapseSidebar
,
"enable_online_topup"
:
setting
.
PayAddress
!=
""
&&
setting
.
EpayId
!=
""
&&
setting
.
EpayKey
!=
""
,
"enable_stripe_topup"
:
setting
.
StripeApiSecret
!=
""
&&
setting
.
StripeWebhookSecret
!=
""
&&
setting
.
StripePriceId
!=
""
,
"enable_creem_topup"
:
setting
.
CreemApiKey
!=
""
&&
setting
.
CreemProducts
!=
"[]"
,
"creem_products"
:
setting
.
CreemProducts
,
"mj_notify_enabled"
:
setting
.
MjNotifyEnabled
,
"chats"
:
setting
.
Chats
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
...
...
controller/topup_creem.go
0 → 100644
View file @
51a7aa44
package
controller
import
(
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"one-api/common"
"one-api/model"
"one-api/setting"
"time"
"github.com/gin-gonic/gin"
"github.com/thanhpk/randstr"
)
const
(
PaymentMethodCreem
=
"creem"
)
var
creemAdaptor
=
&
CreemAdaptor
{}
type
CreemPayRequest
struct
{
ProductId
string
`json:"product_id"`
PaymentMethod
string
`json:"payment_method"`
}
type
CreemProduct
struct
{
ProductId
string
`json:"productId"`
Name
string
`json:"name"`
Price
float64
`json:"price"`
Currency
string
`json:"currency"`
Quota
int64
`json:"quota"`
}
type
CreemAdaptor
struct
{
}
func
(
*
CreemAdaptor
)
RequestPay
(
c
*
gin
.
Context
,
req
*
CreemPayRequest
)
{
if
req
.
PaymentMethod
!=
PaymentMethodCreem
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"不支持的支付渠道"
})
return
}
if
req
.
ProductId
==
""
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"请选择产品"
})
return
}
// 解析产品列表
var
products
[]
CreemProduct
err
:=
json
.
Unmarshal
([]
byte
(
setting
.
CreemProducts
),
&
products
)
if
err
!=
nil
{
log
.
Println
(
"解析Creem产品列表失败"
,
err
)
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"产品配置错误"
})
return
}
// 查找对应的产品
var
selectedProduct
*
CreemProduct
for
_
,
product
:=
range
products
{
if
product
.
ProductId
==
req
.
ProductId
{
selectedProduct
=
&
product
break
}
}
if
selectedProduct
==
nil
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"产品不存在"
})
return
}
id
:=
c
.
GetInt
(
"id"
)
user
,
_
:=
model
.
GetUserById
(
id
,
false
)
reference
:=
fmt
.
Sprintf
(
"creem-api-ref-%d-%d-%s"
,
user
.
Id
,
time
.
Now
()
.
UnixMilli
(),
randstr
.
String
(
4
))
referenceId
:=
"ref_"
+
common
.
Sha1
([]
byte
(
reference
))
checkoutUrl
,
err
:=
genCreemLink
(
referenceId
,
selectedProduct
,
user
.
Email
,
user
.
Username
)
if
err
!=
nil
{
log
.
Println
(
"获取Creem支付链接失败"
,
err
)
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"拉起支付失败"
})
return
}
topUp
:=
&
model
.
TopUp
{
UserId
:
id
,
Amount
:
selectedProduct
.
Quota
,
Money
:
selectedProduct
.
Price
,
TradeNo
:
referenceId
,
CreateTime
:
time
.
Now
()
.
Unix
(),
Status
:
common
.
TopUpStatusPending
,
}
err
=
topUp
.
Insert
()
if
err
!=
nil
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"创建订单失败"
})
return
}
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"success"
,
"data"
:
gin
.
H
{
"checkout_url"
:
checkoutUrl
,
},
})
}
func
RequestCreemPay
(
c
*
gin
.
Context
)
{
var
req
CreemPayRequest
err
:=
c
.
ShouldBindJSON
(
&
req
)
if
err
!=
nil
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"参数错误"
})
return
}
creemAdaptor
.
RequestPay
(
c
,
&
req
)
}
type
CreemWebhookData
struct
{
Type
string
`json:"type"`
Data
struct
{
RequestId
string
`json:"request_id"`
Status
string
`json:"status"`
Metadata
map
[
string
]
string
`json:"metadata"`
}
`json:"data"`
}
func
CreemWebhook
(
c
*
gin
.
Context
)
{
// 解析 webhook 数据
var
webhookData
CreemWebhookData
if
err
:=
c
.
ShouldBindJSON
(
&
webhookData
);
err
!=
nil
{
log
.
Printf
(
"解析Creem Webhook参数失败: %v
\n
"
,
err
)
c
.
AbortWithStatus
(
http
.
StatusBadRequest
)
return
}
// 检查事件类型
if
webhookData
.
Type
!=
"checkout.completed"
{
log
.
Printf
(
"忽略Creem Webhook事件类型: %s"
,
webhookData
.
Type
)
c
.
Status
(
http
.
StatusOK
)
return
}
// 获取引用ID
referenceId
:=
webhookData
.
Data
.
RequestId
if
referenceId
==
""
{
log
.
Println
(
"Creem Webhook缺少request_id字段"
)
c
.
AbortWithStatus
(
http
.
StatusBadRequest
)
return
}
// 处理支付完成事件
err
:=
model
.
RechargeCreem
(
referenceId
)
if
err
!=
nil
{
log
.
Println
(
"Creem充值处理失败:"
,
err
.
Error
(),
referenceId
)
c
.
AbortWithStatus
(
http
.
StatusInternalServerError
)
return
}
log
.
Printf
(
"Creem充值成功: %s"
,
referenceId
)
c
.
Status
(
http
.
StatusOK
)
}
type
CreemCheckoutRequest
struct
{
ProductId
string
`json:"product_id"`
RequestId
string
`json:"request_id"`
Customer
struct
{
Email
string
`json:"email"`
}
`json:"customer"`
Metadata
map
[
string
]
string
`json:"metadata,omitempty"`
}
type
CreemCheckoutResponse
struct
{
CheckoutUrl
string
`json:"checkout_url"`
Id
string
`json:"id"`
}
func
genCreemLink
(
referenceId
string
,
product
*
CreemProduct
,
email
string
,
username
string
)
(
string
,
error
)
{
if
setting
.
CreemApiKey
==
""
{
return
""
,
fmt
.
Errorf
(
"未配置Creem API密钥"
)
}
// 根据测试模式选择 API 端点
apiUrl
:=
"https://api.creem.io/v1/checkouts"
if
setting
.
CreemTestMode
{
apiUrl
=
"https://test-api.creem.io/v1/checkouts"
}
// 构建请求数据
requestData
:=
CreemCheckoutRequest
{
ProductId
:
product
.
ProductId
,
RequestId
:
referenceId
,
Customer
:
struct
{
Email
string
`json:"email"`
}{
Email
:
email
,
},
Metadata
:
map
[
string
]
string
{
"username"
:
username
,
"reference_id"
:
referenceId
,
},
}
// 序列化请求数据
jsonData
,
err
:=
json
.
Marshal
(
requestData
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"序列化请求数据失败: %v"
,
err
)
}
// 创建 HTTP 请求
req
,
err
:=
http
.
NewRequest
(
"POST"
,
apiUrl
,
bytes
.
NewBuffer
(
jsonData
))
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"创建HTTP请求失败: %v"
,
err
)
}
// 设置请求头
req
.
Header
.
Set
(
"Content-Type"
,
"application/json"
)
req
.
Header
.
Set
(
"x-api-key"
,
setting
.
CreemApiKey
)
// 发送请求
client
:=
&
http
.
Client
{
Timeout
:
30
*
time
.
Second
,
}
resp
,
err
:=
client
.
Do
(
req
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"发送HTTP请求失败: %v"
,
err
)
}
defer
resp
.
Body
.
Close
()
log
.
Printf
(
" creem req host: %s, key %s req 【%s】"
,
apiUrl
,
setting
.
CreemApiKey
,
jsonData
)
// 读取响应
body
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"读取响应失败: %v"
,
err
)
}
// 检查响应状态
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
""
,
fmt
.
Errorf
(
"Creem API 返回错误状态 %d: %s"
,
resp
.
StatusCode
,
string
(
body
))
}
// 解析响应
var
checkoutResp
CreemCheckoutResponse
err
=
json
.
Unmarshal
(
body
,
&
checkoutResp
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"解析响应失败: %v"
,
err
)
}
if
checkoutResp
.
CheckoutUrl
==
""
{
return
""
,
fmt
.
Errorf
(
"Creem API 未返回支付链接"
)
}
log
.
Printf
(
"Creem 支付链接创建成功: %s, 订单ID: %s"
,
referenceId
,
checkoutResp
.
Id
)
return
checkoutResp
.
CheckoutUrl
,
nil
}
model/option.go
View file @
51a7aa44
...
...
@@ -81,6 +81,9 @@ func InitOptionMap() {
common
.
OptionMap
[
"StripeWebhookSecret"
]
=
setting
.
StripeWebhookSecret
common
.
OptionMap
[
"StripePriceId"
]
=
setting
.
StripePriceId
common
.
OptionMap
[
"StripeUnitPrice"
]
=
strconv
.
FormatFloat
(
setting
.
StripeUnitPrice
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"CreemApiKey"
]
=
setting
.
CreemApiKey
common
.
OptionMap
[
"CreemProducts"
]
=
setting
.
CreemProducts
common
.
OptionMap
[
"CreemTestMode"
]
=
strconv
.
FormatBool
(
setting
.
CreemTestMode
)
common
.
OptionMap
[
"TopupGroupRatio"
]
=
common
.
TopupGroupRatio2JSONString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
...
...
@@ -326,6 +329,12 @@ func updateOptionMap(key string, value string) (err error) {
setting
.
StripeUnitPrice
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"StripeMinTopUp"
:
setting
.
StripeMinTopUp
,
_
=
strconv
.
Atoi
(
value
)
case
"CreemApiKey"
:
setting
.
CreemApiKey
=
value
case
"CreemProducts"
:
setting
.
CreemProducts
=
value
case
"CreemTestMode"
:
setting
.
CreemTestMode
=
value
==
"true"
case
"TopupGroupRatio"
:
err
=
common
.
UpdateTopupGroupRatioByJSONString
(
value
)
case
"GitHubClientId"
:
...
...
model/topup.go
View file @
51a7aa44
...
...
@@ -98,3 +98,52 @@ func Recharge(referenceId string, customerId string) (err error) {
return
nil
}
func
RechargeCreem
(
referenceId
string
)
(
err
error
)
{
if
referenceId
==
""
{
return
errors
.
New
(
"未提供支付单号"
)
}
var
quota
float64
topUp
:=
&
TopUp
{}
refCol
:=
"`trade_no`"
if
common
.
UsingPostgreSQL
{
refCol
=
`"trade_no"`
}
err
=
DB
.
Transaction
(
func
(
tx
*
gorm
.
DB
)
error
{
err
:=
tx
.
Set
(
"gorm:query_option"
,
"FOR UPDATE"
)
.
Where
(
refCol
+
" = ?"
,
referenceId
)
.
First
(
topUp
)
.
Error
if
err
!=
nil
{
return
errors
.
New
(
"充值订单不存在"
)
}
if
topUp
.
Status
!=
common
.
TopUpStatusPending
{
return
errors
.
New
(
"充值订单状态错误"
)
}
topUp
.
CompleteTime
=
common
.
GetTimestamp
()
topUp
.
Status
=
common
.
TopUpStatusSuccess
err
=
tx
.
Save
(
topUp
)
.
Error
if
err
!=
nil
{
return
err
}
// Creem 直接使用 Amount 作为充值额度
quota
=
float64
(
topUp
.
Amount
)
err
=
tx
.
Model
(
&
User
{})
.
Where
(
"id = ?"
,
topUp
.
UserId
)
.
Update
(
"quota"
,
gorm
.
Expr
(
"quota + ?"
,
quota
))
.
Error
if
err
!=
nil
{
return
err
}
return
nil
})
if
err
!=
nil
{
return
errors
.
New
(
"充值失败,"
+
err
.
Error
())
}
RecordLog
(
topUp
.
UserId
,
LogTypeTopup
,
fmt
.
Sprintf
(
"使用Creem充值成功,充值额度: %v,支付金额:%.2f"
,
common
.
FormatQuota
(
int
(
quota
)),
topUp
.
Money
))
return
nil
}
router/api-router.go
View file @
51a7aa44
...
...
@@ -39,6 +39,7 @@ func SetApiRouter(router *gin.Engine) {
apiRouter
.
GET
(
"/ratio_config"
,
middleware
.
CriticalRateLimit
(),
controller
.
GetRatioConfig
)
apiRouter
.
POST
(
"/stripe/webhook"
,
controller
.
StripeWebhook
)
apiRouter
.
POST
(
"/creem/webhook"
,
controller
.
CreemWebhook
)
userRoute
:=
apiRouter
.
Group
(
"/user"
)
{
...
...
@@ -64,6 +65,7 @@ func SetApiRouter(router *gin.Engine) {
selfRoute
.
POST
(
"/amount"
,
controller
.
RequestAmount
)
selfRoute
.
POST
(
"/stripe/pay"
,
middleware
.
CriticalRateLimit
(),
controller
.
RequestStripePay
)
selfRoute
.
POST
(
"/stripe/amount"
,
controller
.
RequestStripeAmount
)
selfRoute
.
POST
(
"/creem/pay"
,
middleware
.
CriticalRateLimit
(),
controller
.
RequestCreemPay
)
selfRoute
.
POST
(
"/aff_transfer"
,
controller
.
TransferAffQuota
)
selfRoute
.
PUT
(
"/setting"
,
controller
.
UpdateUserSetting
)
}
...
...
setting/payment_creem.go
0 → 100644
View file @
51a7aa44
package
setting
var
CreemApiKey
=
""
var
CreemProducts
=
"[]"
var
CreemTestMode
=
false
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