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
81d474ba
authored
Jul 10, 2025
by
wzxjohn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add stripe pay api support
parent
4a02c5e2
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
420 additions
and
10 deletions
+420
-10
common/constants.go
+6
-0
common/hash.go
+34
-0
controller/misc.go
+3
-0
controller/topup_stripe.go
+276
-0
go.mod
+3
-1
go.sum
+6
-0
model/option.go
+15
-0
model/topup.go
+63
-7
model/user.go
+1
-0
router/api-router.go
+6
-2
setting/payment_stripe.go
+7
-0
No files found.
common/constants.go
View file @
81d474ba
...
...
@@ -193,3 +193,9 @@ const (
ChannelStatusManuallyDisabled
=
2
// also don't use 0
ChannelStatusAutoDisabled
=
3
)
const
(
TopUpStatusPending
=
"pending"
TopUpStatusSuccess
=
"success"
TopUpStatusExpired
=
"expired"
)
common/hash.go
0 → 100644
View file @
81d474ba
package
common
import
(
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
)
func
Sha256Raw
(
data
string
)
[]
byte
{
h
:=
sha256
.
New
()
h
.
Write
([]
byte
(
data
))
return
h
.
Sum
(
nil
)
}
func
Sha1Raw
(
data
[]
byte
)
[]
byte
{
h
:=
sha1
.
New
()
h
.
Write
([]
byte
(
data
))
return
h
.
Sum
(
nil
)
}
func
Sha1
(
data
string
)
string
{
return
hex
.
EncodeToString
(
Sha1Raw
([]
byte
(
data
)))
}
func
HmacSha256Raw
(
message
,
key
[]
byte
)
[]
byte
{
h
:=
hmac
.
New
(
sha256
.
New
,
key
)
h
.
Write
(
message
)
return
h
.
Sum
(
nil
)
}
func
HmacSha256
(
message
,
key
string
)
string
{
return
hex
.
EncodeToString
(
HmacSha256Raw
([]
byte
(
message
),
[]
byte
(
key
)))
}
controller/misc.go
View file @
81d474ba
...
...
@@ -57,7 +57,9 @@ func GetStatus(c *gin.Context) {
"wechat_login"
:
common
.
WeChatAuthEnabled
,
"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_site_key"
:
common
.
TurnstileSiteKey
,
"top_up_link"
:
common
.
TopUpLink
,
...
...
@@ -71,6 +73,7 @@ func GetStatus(c *gin.Context) {
"data_export_default_time"
:
common
.
DataExportDefaultTime
,
"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
,
"chats"
:
setting
.
Chats
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
...
...
controller/topup_stripe.go
0 → 100644
View file @
81d474ba
package
controller
import
(
"fmt"
"io"
"log"
"net/http"
"one-api/common"
"one-api/model"
"one-api/setting"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/stripe/stripe-go/v81"
"github.com/stripe/stripe-go/v81/checkout/session"
"github.com/stripe/stripe-go/v81/webhook"
"github.com/thanhpk/randstr"
)
const
(
PaymentMethodStripe
=
"stripe"
)
var
stripeAdaptor
=
&
StripeAdaptor
{}
type
StripePayRequest
struct
{
Amount
int64
`json:"amount"`
PaymentMethod
string
`json:"payment_method"`
TopUpCode
string
`json:"top_up_code"`
}
type
StripeAdaptor
struct
{
}
func
(
*
StripeAdaptor
)
RequestAmount
(
c
*
gin
.
Context
,
req
*
StripePayRequest
)
{
if
req
.
Amount
<
getStripeMinTopup
()
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
fmt
.
Sprintf
(
"充值数量不能小于 %d"
,
getStripeMinTopup
())})
return
}
id
:=
c
.
GetInt
(
"id"
)
group
,
err
:=
model
.
GetUserGroup
(
id
,
true
)
if
err
!=
nil
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"获取用户分组失败"
})
return
}
payMoney
:=
getStripePayMoney
(
float64
(
req
.
Amount
),
group
)
if
payMoney
<=
0.01
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"充值金额过低"
})
return
}
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"success"
,
"data"
:
strconv
.
FormatFloat
(
payMoney
,
'f'
,
2
,
64
)})
}
func
(
*
StripeAdaptor
)
RequestPay
(
c
*
gin
.
Context
,
req
*
StripePayRequest
)
{
if
req
.
PaymentMethod
!=
PaymentMethodStripe
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"不支持的支付渠道"
})
return
}
if
req
.
Amount
<
int64
(
setting
.
StripeMinTopUp
)
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
fmt
.
Sprintf
(
"充值数量不能小于 %d"
,
setting
.
StripeMinTopUp
),
"data"
:
10
})
return
}
if
req
.
Amount
>
10000
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"充值数量不能大于 10000"
,
"data"
:
10
})
return
}
id
:=
c
.
GetInt
(
"id"
)
user
,
_
:=
model
.
GetUserById
(
id
,
false
)
chargedMoney
:=
GetChargedAmount
(
float64
(
req
.
Amount
),
*
user
)
reference
:=
fmt
.
Sprintf
(
"new-api-ref-%d-%d-%s"
,
user
.
Id
,
time
.
Now
()
.
UnixMilli
(),
randstr
.
String
(
4
))
referenceId
:=
"ref_"
+
common
.
Sha1
(
reference
)
payLink
,
err
:=
genStripeLink
(
referenceId
,
user
.
StripeCustomer
,
user
.
Email
,
req
.
Amount
)
if
err
!=
nil
{
log
.
Println
(
"获取Stripe Checkout支付链接失败"
,
err
)
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"拉起支付失败"
})
return
}
topUp
:=
&
model
.
TopUp
{
UserId
:
id
,
Amount
:
req
.
Amount
,
Money
:
chargedMoney
,
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
{
"pay_link"
:
payLink
,
},
})
}
func
RequestStripeAmount
(
c
*
gin
.
Context
)
{
var
req
StripePayRequest
err
:=
c
.
ShouldBindJSON
(
&
req
)
if
err
!=
nil
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"参数错误"
})
return
}
stripeAdaptor
.
RequestAmount
(
c
,
&
req
)
}
func
RequestStripePay
(
c
*
gin
.
Context
)
{
var
req
StripePayRequest
err
:=
c
.
ShouldBindJSON
(
&
req
)
if
err
!=
nil
{
c
.
JSON
(
200
,
gin
.
H
{
"message"
:
"error"
,
"data"
:
"参数错误"
})
return
}
stripeAdaptor
.
RequestPay
(
c
,
&
req
)
}
func
StripeWebhook
(
c
*
gin
.
Context
)
{
payload
,
err
:=
io
.
ReadAll
(
c
.
Request
.
Body
)
if
err
!=
nil
{
log
.
Printf
(
"解析Stripe Webhook参数失败: %v
\n
"
,
err
)
c
.
AbortWithStatus
(
http
.
StatusServiceUnavailable
)
return
}
signature
:=
c
.
GetHeader
(
"Stripe-Signature"
)
endpointSecret
:=
setting
.
StripeWebhookSecret
event
,
err
:=
webhook
.
ConstructEventWithOptions
(
payload
,
signature
,
endpointSecret
,
webhook
.
ConstructEventOptions
{
IgnoreAPIVersionMismatch
:
true
,
})
if
err
!=
nil
{
log
.
Printf
(
"Stripe Webhook验签失败: %v
\n
"
,
err
)
c
.
AbortWithStatus
(
http
.
StatusBadRequest
)
return
}
switch
event
.
Type
{
case
stripe
.
EventTypeCheckoutSessionCompleted
:
sessionCompleted
(
event
)
case
stripe
.
EventTypeCheckoutSessionExpired
:
sessionExpired
(
event
)
default
:
log
.
Printf
(
"不支持的Stripe Webhook事件类型: %s
\n
"
,
event
.
Type
)
}
c
.
Status
(
http
.
StatusOK
)
}
func
sessionCompleted
(
event
stripe
.
Event
)
{
customerId
:=
event
.
GetObjectValue
(
"customer"
)
referenceId
:=
event
.
GetObjectValue
(
"client_reference_id"
)
status
:=
event
.
GetObjectValue
(
"status"
)
if
"complete"
!=
status
{
log
.
Println
(
"错误的Stripe Checkout完成状态:"
,
status
,
","
,
referenceId
)
return
}
err
:=
model
.
Recharge
(
referenceId
,
customerId
)
if
err
!=
nil
{
log
.
Println
(
err
.
Error
(),
referenceId
)
return
}
total
,
_
:=
strconv
.
ParseFloat
(
event
.
GetObjectValue
(
"amount_total"
),
64
)
currency
:=
strings
.
ToUpper
(
event
.
GetObjectValue
(
"currency"
))
log
.
Printf
(
"收到款项:%s, %.2f(%s)"
,
referenceId
,
total
/
100
,
currency
)
}
func
sessionExpired
(
event
stripe
.
Event
)
{
referenceId
:=
event
.
GetObjectValue
(
"client_reference_id"
)
status
:=
event
.
GetObjectValue
(
"status"
)
if
"expired"
!=
status
{
log
.
Println
(
"错误的Stripe Checkout过期状态:"
,
status
,
","
,
referenceId
)
return
}
if
""
==
referenceId
{
log
.
Println
(
"未提供支付单号"
)
return
}
topUp
:=
model
.
GetTopUpByTradeNo
(
referenceId
)
if
topUp
==
nil
{
log
.
Println
(
"充值订单不存在"
,
referenceId
)
return
}
if
topUp
.
Status
!=
common
.
TopUpStatusPending
{
log
.
Println
(
"充值订单状态错误"
,
referenceId
)
}
topUp
.
Status
=
common
.
TopUpStatusExpired
err
:=
topUp
.
Update
()
if
err
!=
nil
{
log
.
Println
(
"过期充值订单失败"
,
referenceId
,
", err:"
,
err
.
Error
())
return
}
log
.
Println
(
"充值订单已过期"
,
referenceId
)
}
func
genStripeLink
(
referenceId
string
,
customerId
string
,
email
string
,
amount
int64
)
(
string
,
error
)
{
if
!
strings
.
HasPrefix
(
setting
.
StripeApiSecret
,
"sk_"
)
&&
!
strings
.
HasPrefix
(
setting
.
StripeApiSecret
,
"rk_"
)
{
return
""
,
fmt
.
Errorf
(
"无效的Stripe API密钥"
)
}
stripe
.
Key
=
setting
.
StripeApiSecret
params
:=
&
stripe
.
CheckoutSessionParams
{
ClientReferenceID
:
stripe
.
String
(
referenceId
),
SuccessURL
:
stripe
.
String
(
setting
.
ServerAddress
+
"/log"
),
CancelURL
:
stripe
.
String
(
setting
.
ServerAddress
+
"/topup"
),
LineItems
:
[]
*
stripe
.
CheckoutSessionLineItemParams
{
{
Price
:
stripe
.
String
(
setting
.
StripePriceId
),
Quantity
:
stripe
.
Int64
(
amount
),
},
},
Mode
:
stripe
.
String
(
string
(
stripe
.
CheckoutSessionModePayment
)),
}
if
""
==
customerId
{
if
""
!=
email
{
params
.
CustomerEmail
=
stripe
.
String
(
email
)
}
params
.
CustomerCreation
=
stripe
.
String
(
string
(
stripe
.
CheckoutSessionCustomerCreationAlways
))
}
else
{
params
.
Customer
=
stripe
.
String
(
customerId
)
}
result
,
err
:=
session
.
New
(
params
)
if
err
!=
nil
{
return
""
,
err
}
return
result
.
URL
,
nil
}
func
GetChargedAmount
(
count
float64
,
user
model
.
User
)
float64
{
topUpGroupRatio
:=
common
.
GetTopupGroupRatio
(
user
.
Group
)
if
topUpGroupRatio
==
0
{
topUpGroupRatio
=
1
}
return
count
*
topUpGroupRatio
}
func
getStripePayMoney
(
amount
float64
,
group
string
)
float64
{
if
!
common
.
DisplayInCurrencyEnabled
{
amount
=
amount
/
common
.
QuotaPerUnit
}
// 别问为什么用float64,问就是这么点钱没必要
topupGroupRatio
:=
common
.
GetTopupGroupRatio
(
group
)
if
topupGroupRatio
==
0
{
topupGroupRatio
=
1
}
payMoney
:=
amount
*
setting
.
StripeUnitPrice
*
topupGroupRatio
return
payMoney
}
func
getStripeMinTopup
()
int64
{
minTopup
:=
setting
.
StripeMinTopUp
if
!
common
.
DisplayInCurrencyEnabled
{
minTopup
=
minTopup
*
int
(
common
.
QuotaPerUnit
)
}
return
int64
(
minTopup
)
}
go.mod
View file @
81d474ba
...
...
@@ -27,10 +27,13 @@ require (
github.com/samber/lo v1.39.0
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shopspring/decimal v1.4.0
github.com/stripe/stripe-go/v81 v81.4.0
github.com/thanhpk/randstr v1.0.6
github.com/tiktoken-go/tokenizer v0.6.2
golang.org/x/crypto v0.35.0
golang.org/x/image v0.23.0
golang.org/x/net v0.35.0
golang.org/x/sync v0.11.0
gorm.io/driver/mysql v1.4.3
gorm.io/driver/postgres v1.5.2
gorm.io/gorm v1.25.2
...
...
@@ -84,7 +87,6 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
...
...
go.sum
View file @
81d474ba
...
...
@@ -195,6 +195,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stripe/stripe-go/v81 v81.4.0 h1:AuD9XzdAvl193qUCSaLocf8H+nRopOouXhxqJUzCLbw=
github.com/stripe/stripe-go/v81 v81.4.0/go.mod h1:C/F4jlmnGNacvYtBp/LUHCvVUJEZffFQCobkzwY1WOo=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/tiktoken-go/tokenizer v0.6.2 h1:t0GN2DvcUZSFWT/62YOgoqb10y7gSXBGs0A+4VCQK+g=
github.com/tiktoken-go/tokenizer v0.6.2/go.mod h1:6UCYI/DtOallbmL7sSy30p6YQv60qNyU/4aVigPOx6w=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
...
...
@@ -224,6 +228,7 @@ golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSO
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
...
...
@@ -232,6 +237,7 @@ golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
...
...
model/option.go
View file @
81d474ba
...
...
@@ -75,6 +75,11 @@ func InitOptionMap() {
common
.
OptionMap
[
"EpayKey"
]
=
""
common
.
OptionMap
[
"Price"
]
=
strconv
.
FormatFloat
(
setting
.
Price
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"MinTopUp"
]
=
strconv
.
Itoa
(
setting
.
MinTopUp
)
common
.
OptionMap
[
"StripeMinTopUp"
]
=
strconv
.
Itoa
(
setting
.
StripeMinTopUp
)
common
.
OptionMap
[
"StripeApiSecret"
]
=
setting
.
StripeApiSecret
common
.
OptionMap
[
"StripeWebhookSecret"
]
=
setting
.
StripeWebhookSecret
common
.
OptionMap
[
"StripePriceId"
]
=
setting
.
StripePriceId
common
.
OptionMap
[
"StripeUnitPrice"
]
=
strconv
.
FormatFloat
(
setting
.
StripeUnitPrice
,
'f'
,
-
1
,
64
)
common
.
OptionMap
[
"TopupGroupRatio"
]
=
common
.
TopupGroupRatio2JSONString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
...
...
@@ -308,6 +313,16 @@ func updateOptionMap(key string, value string) (err error) {
setting
.
Price
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"MinTopUp"
:
setting
.
MinTopUp
,
_
=
strconv
.
Atoi
(
value
)
case
"StripeApiSecret"
:
setting
.
StripeApiSecret
=
value
case
"StripeWebhookSecret"
:
setting
.
StripeWebhookSecret
=
value
case
"StripePriceId"
:
setting
.
StripePriceId
=
value
case
"StripeUnitPrice"
:
setting
.
StripeUnitPrice
,
_
=
strconv
.
ParseFloat
(
value
,
64
)
case
"StripeMinTopUp"
:
setting
.
StripeMinTopUp
,
_
=
strconv
.
Atoi
(
value
)
case
"TopupGroupRatio"
:
err
=
common
.
UpdateTopupGroupRatioByJSONString
(
value
)
case
"GitHubClientId"
:
...
...
model/topup.go
View file @
81d474ba
package
model
import
(
"errors"
"fmt"
"gorm.io/gorm"
"one-api/common"
)
type
TopUp
struct
{
Id
int
`json:"id"`
UserId
int
`json:"user_id" gorm:"index"`
Amount
int64
`json:"amount"`
Money
float64
`json:"money"`
TradeNo
string
`json:"trade_no"`
CreateTime
int64
`json:"create_time"`
Status
string
`json:"status"`
Id
int
`json:"id"`
UserId
int
`json:"user_id" gorm:"index"`
Amount
int64
`json:"amount"`
Money
float64
`json:"money"`
TradeNo
string
`json:"trade_no" gorm:"unique"`
CreateTime
int64
`json:"create_time"`
CompleteTime
int64
`json:"complete_time"`
Status
string
`json:"status"`
}
func
(
topUp
*
TopUp
)
Insert
()
error
{
...
...
@@ -41,3 +49,51 @@ func GetTopUpByTradeNo(tradeNo string) *TopUp {
}
return
topUp
}
func
Recharge
(
referenceId
string
,
customerId
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
}
quota
=
topUp
.
Money
*
common
.
QuotaPerUnit
err
=
tx
.
Model
(
&
User
{})
.
Where
(
"id = ?"
,
topUp
.
UserId
)
.
Updates
(
map
[
string
]
interface
{}{
"stripe_customer"
:
customerId
,
"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
(
"使用在线充值成功,充值金额: %v,支付金额:%d"
,
common
.
FormatQuota
(
int
(
quota
)),
topUp
.
Amount
))
return
nil
}
model/user.go
View file @
81d474ba
...
...
@@ -43,6 +43,7 @@ type User struct {
LinuxDOId
string
`json:"linux_do_id" gorm:"column:linux_do_id;index"`
Setting
string
`json:"setting" gorm:"type:text;column:setting"`
Remark
string
`json:"remark,omitempty" gorm:"type:varchar(255)" validate:"max=255"`
StripeCustomer
string
`json:"stripe_customer" gorm:"column:stripe_customer;index"`
}
func
(
user
*
User
)
ToBaseUser
()
*
UserBase
{
...
...
router/api-router.go
View file @
81d474ba
...
...
@@ -38,6 +38,8 @@ func SetApiRouter(router *gin.Engine) {
apiRouter
.
GET
(
"/oauth/telegram/bind"
,
middleware
.
CriticalRateLimit
(),
controller
.
TelegramBind
)
apiRouter
.
GET
(
"/ratio_config"
,
middleware
.
CriticalRateLimit
(),
controller
.
GetRatioConfig
)
apiRouter
.
POST
(
"/stripe/webhook"
,
controller
.
StripeWebhook
)
userRoute
:=
apiRouter
.
Group
(
"/user"
)
{
userRoute
.
POST
(
"/register"
,
middleware
.
CriticalRateLimit
(),
middleware
.
TurnstileCheck
(),
controller
.
Register
)
...
...
@@ -57,9 +59,11 @@ func SetApiRouter(router *gin.Engine) {
selfRoute
.
DELETE
(
"/self"
,
controller
.
DeleteSelf
)
selfRoute
.
GET
(
"/token"
,
controller
.
GenerateAccessToken
)
selfRoute
.
GET
(
"/aff"
,
controller
.
GetAffCode
)
selfRoute
.
POST
(
"/topup"
,
controller
.
TopUp
)
selfRoute
.
POST
(
"/pay"
,
controller
.
RequestEpay
)
selfRoute
.
POST
(
"/topup"
,
middleware
.
CriticalRateLimit
(),
controller
.
TopUp
)
selfRoute
.
POST
(
"/pay"
,
middleware
.
CriticalRateLimit
(),
controller
.
RequestEpay
)
selfRoute
.
POST
(
"/amount"
,
controller
.
RequestAmount
)
selfRoute
.
POST
(
"/stripe/pay"
,
middleware
.
CriticalRateLimit
(),
controller
.
RequestStripePay
)
selfRoute
.
POST
(
"/stripe/amount"
,
controller
.
RequestStripeAmount
)
selfRoute
.
POST
(
"/aff_transfer"
,
controller
.
TransferAffQuota
)
selfRoute
.
PUT
(
"/setting"
,
controller
.
UpdateUserSetting
)
}
...
...
setting/payment_stripe.go
0 → 100644
View file @
81d474ba
package
setting
var
StripeApiSecret
=
""
var
StripeWebhookSecret
=
""
var
StripePriceId
=
""
var
StripeUnitPrice
=
8.0
var
StripeMinTopUp
=
1
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