mirror of
https://github.com/linux-do/new-api.git
synced 2025-10-19 05:43:42 +08:00
replace epay with stripe
Signed-off-by: wozulong <>
This commit is contained in:
@@ -11,12 +11,12 @@ import (
|
||||
|
||||
// Pay Settings
|
||||
|
||||
var PayAddress = ""
|
||||
var CustomCallbackAddress = ""
|
||||
var EpayId = ""
|
||||
var EpayKey = ""
|
||||
var Price = 7.3
|
||||
var MinTopUp = 1
|
||||
var StripeApiSecret = ""
|
||||
var StripeWebhookSecret = ""
|
||||
var StripePriceId = ""
|
||||
var PaymentEnabled = false
|
||||
var StripeUnitPrice = 8.0
|
||||
var MinTopUp = 5
|
||||
|
||||
var StartTime = time.Now().Unix() // unit: second
|
||||
var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change
|
||||
@@ -188,6 +188,12 @@ const (
|
||||
ChannelStatusAutoDisabled = 3
|
||||
)
|
||||
|
||||
const (
|
||||
TopUpStatusPending = "pending"
|
||||
TopUpStatusSuccess = "success"
|
||||
TopUpStatusExpired = "expired"
|
||||
)
|
||||
|
||||
const (
|
||||
ChannelTypeUnknown = 0
|
||||
ChannelTypeOpenAI = 1
|
||||
|
84
common/hash.go
Normal file
84
common/hash.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
func RandomBytes(length int) []byte {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
b := make([]byte, length)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func RandomString(length int) string {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
result := make([]byte, length)
|
||||
randomBytes := RandomBytes(length)
|
||||
for i := 0; i < length; i++ {
|
||||
result[i] = chars[randomBytes[i]%byte(len(chars))]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func RandomHex(length int) string {
|
||||
const chars = "abcdef0123456789"
|
||||
result := make([]byte, length)
|
||||
randomBytes := RandomBytes(length)
|
||||
for i := 0; i < length; i++ {
|
||||
result[i] = chars[randomBytes[i]%byte(len(chars))]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func RandomNumber(length int) string {
|
||||
const chars = "0123456789"
|
||||
result := make([]byte, length)
|
||||
randomBytes := RandomBytes(length)
|
||||
for i := 0; i < length; i++ {
|
||||
result[i] = chars[randomBytes[i]%byte(len(chars))]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func RandomUUID() string {
|
||||
all := RandomHex(32)
|
||||
return all[:8] + "-" + all[8:12] + "-" + all[12:16] + "-" + all[16:20] + "-" + all[20:]
|
||||
}
|
@@ -98,3 +98,11 @@ func LogQuota(quota int) string {
|
||||
return fmt.Sprintf("%d 点额度", quota)
|
||||
}
|
||||
}
|
||||
|
||||
func LogQuotaF(quota float64) string {
|
||||
if DisplayInCurrencyEnabled {
|
||||
return fmt.Sprintf("$%.6f 额度", quota/QuotaPerUnit)
|
||||
} else {
|
||||
return fmt.Sprintf("%d 点额度", int64(quota))
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ func GetStatus(c *gin.Context) {
|
||||
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
|
||||
"wechat_login": common.WeChatAuthEnabled,
|
||||
"server_address": common.ServerAddress,
|
||||
"price": common.Price,
|
||||
"stripe_unit_price": common.StripeUnitPrice,
|
||||
"min_topup": common.MinTopUp,
|
||||
"turnstile_check": common.TurnstileCheckEnabled,
|
||||
"turnstile_site_key": common.TurnstileSiteKey,
|
||||
@@ -61,7 +61,7 @@ func GetStatus(c *gin.Context) {
|
||||
"enable_data_export": common.DataExportEnabled,
|
||||
"data_export_default_time": common.DataExportDefaultTime,
|
||||
"default_collapse_sidebar": common.DefaultCollapseSidebar,
|
||||
"enable_online_topup": common.PayAddress != "" && common.EpayId != "" && common.EpayKey != "",
|
||||
"payment_enabled": common.PaymentEnabled,
|
||||
"mj_notify_enabled": constant.MjNotifyEnabled,
|
||||
"version": common.Version,
|
||||
},
|
||||
|
96
controller/stripe.go
Normal file
96
controller/stripe.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stripe/stripe-go/v76"
|
||||
"github.com/stripe/stripe-go/v76/webhook"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"one-api/common"
|
||||
"one-api/model"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 := common.StripeWebhookSecret
|
||||
event, err := webhook.ConstructEvent(payload, signature, endpointSecret)
|
||||
|
||||
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) {
|
||||
referenceId := event.GetObjectValue("client_reference_id")
|
||||
status := event.GetObjectValue("status")
|
||||
if "complete" != status {
|
||||
log.Println("错误的Stripe Checkout完成状态:", status, ",", referenceId)
|
||||
return
|
||||
}
|
||||
|
||||
err := model.Recharge(referenceId)
|
||||
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)
|
||||
}
|
@@ -1,21 +1,20 @@
|
||||
package controller
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samber/lo"
|
||||
epay "github.com/star-horizon/go-epay"
|
||||
"github.com/stripe/stripe-go/v76"
|
||||
"github.com/stripe/stripe-go/v76/checkout/session"
|
||||
"log"
|
||||
"net/url"
|
||||
"one-api/common"
|
||||
"one-api/model"
|
||||
"one-api/service"
|
||||
"strconv"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EpayRequest struct {
|
||||
type PayRequest struct {
|
||||
Amount int `json:"amount"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
TopUpCode string `json:"top_up_code"`
|
||||
@@ -26,177 +25,103 @@ type AmountRequest struct {
|
||||
TopUpCode string `json:"top_up_code"`
|
||||
}
|
||||
|
||||
func GetEpayClient() *epay.Client {
|
||||
if common.PayAddress == "" || common.EpayId == "" || common.EpayKey == "" {
|
||||
return nil
|
||||
func genStripeLink(referenceId string, amount int64) (string, error) {
|
||||
if !strings.HasPrefix(common.StripeApiSecret, "sk_") {
|
||||
return "", fmt.Errorf("无效的Stripe API密钥")
|
||||
}
|
||||
withUrl, err := epay.NewClientWithUrl(&epay.Config{
|
||||
PartnerID: common.EpayId,
|
||||
Key: common.EpayKey,
|
||||
}, common.PayAddress)
|
||||
|
||||
stripe.Key = common.StripeApiSecret
|
||||
|
||||
params := &stripe.CheckoutSessionParams{
|
||||
ClientReferenceID: stripe.String(referenceId),
|
||||
SuccessURL: stripe.String(common.ServerAddress + "/log"),
|
||||
CancelURL: stripe.String(common.ServerAddress + "/topup"),
|
||||
LineItems: []*stripe.CheckoutSessionLineItemParams{
|
||||
{
|
||||
Price: stripe.String(common.StripePriceId),
|
||||
Quantity: stripe.Int64(amount),
|
||||
},
|
||||
},
|
||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||
}
|
||||
result, err := session.New(params)
|
||||
if err != nil {
|
||||
return nil
|
||||
return "", err
|
||||
}
|
||||
return withUrl
|
||||
|
||||
return result.URL, nil
|
||||
}
|
||||
|
||||
func GetAmount(count float64, user model.User) float64 {
|
||||
// 别问为什么用float64,问就是这么点钱没必要
|
||||
topupGroupRatio := common.GetTopupGroupRatio(user.Group)
|
||||
if topupGroupRatio == 0 {
|
||||
topupGroupRatio = 1
|
||||
}
|
||||
amount := count * common.Price * topupGroupRatio
|
||||
return amount
|
||||
func GetPayAmount(count float64) float64 {
|
||||
return count * common.StripeUnitPrice
|
||||
}
|
||||
|
||||
func RequestEpay(c *gin.Context) {
|
||||
var req EpayRequest
|
||||
func GetChargedAmount(count float64, user model.User) float64 {
|
||||
topUpGroupRatio := common.GetTopupGroupRatio(user.Group)
|
||||
if topUpGroupRatio == 0 {
|
||||
topUpGroupRatio = 1
|
||||
}
|
||||
|
||||
return count * topUpGroupRatio
|
||||
}
|
||||
|
||||
func RequestPayLink(c *gin.Context) {
|
||||
var req PayRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{"message": err.Error(), "data": 10})
|
||||
return
|
||||
}
|
||||
if !common.PaymentEnabled {
|
||||
c.JSON(200, gin.H{"message": "error", "data": "管理员未开启在线支付"})
|
||||
return
|
||||
}
|
||||
if req.PaymentMethod != "stripe" {
|
||||
c.JSON(200, gin.H{"message": "error", "data": "不支持的支付渠道"})
|
||||
return
|
||||
}
|
||||
if req.Amount < common.MinTopUp {
|
||||
c.JSON(200, gin.H{"message": fmt.Sprintf("充值数量不能小于 %d", common.MinTopUp), "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)
|
||||
payMoney := GetAmount(float64(req.Amount), *user)
|
||||
chargedMoney := GetChargedAmount(float64(req.Amount), *user)
|
||||
|
||||
var payType epay.PurchaseType
|
||||
if req.PaymentMethod == "zfb" {
|
||||
payType = epay.Alipay
|
||||
}
|
||||
if req.PaymentMethod == "wx" {
|
||||
req.PaymentMethod = "wxpay"
|
||||
payType = epay.WechatPay
|
||||
}
|
||||
callBackAddress := service.GetCallbackAddress()
|
||||
returnUrl, _ := url.Parse(common.ServerAddress + "/log")
|
||||
notifyUrl, _ := url.Parse(callBackAddress + "/api/user/epay/notify")
|
||||
tradeNo := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
client := GetEpayClient()
|
||||
if client == nil {
|
||||
c.JSON(200, gin.H{"message": "error", "data": "当前管理员未配置支付信息"})
|
||||
return
|
||||
}
|
||||
uri, params, err := client.Purchase(&epay.PurchaseArgs{
|
||||
Type: payType,
|
||||
ServiceTradeNo: "A" + tradeNo,
|
||||
Name: "B" + tradeNo,
|
||||
Money: strconv.FormatFloat(payMoney, 'f', 2, 64),
|
||||
Device: epay.PC,
|
||||
NotifyUrl: notifyUrl,
|
||||
ReturnUrl: returnUrl,
|
||||
})
|
||||
reference := fmt.Sprintf("new-api-ref-%d-%d-%s", user.Id, time.Now().UnixMilli(), common.RandomString(4))
|
||||
referenceId := "ref_" + common.Sha1(reference)
|
||||
|
||||
payLink, err := genStripeLink(referenceId, int64(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: payMoney,
|
||||
TradeNo: "A" + tradeNo,
|
||||
Money: chargedMoney,
|
||||
TradeNo: referenceId,
|
||||
CreateTime: time.Now().Unix(),
|
||||
Status: "pending",
|
||||
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": params, "url": uri})
|
||||
}
|
||||
|
||||
// tradeNo lock
|
||||
var orderLocks sync.Map
|
||||
var createLock sync.Mutex
|
||||
|
||||
// LockOrder 尝试对给定订单号加锁
|
||||
func LockOrder(tradeNo string) {
|
||||
lock, ok := orderLocks.Load(tradeNo)
|
||||
if !ok {
|
||||
createLock.Lock()
|
||||
defer createLock.Unlock()
|
||||
lock, ok = orderLocks.Load(tradeNo)
|
||||
if !ok {
|
||||
lock = new(sync.Mutex)
|
||||
orderLocks.Store(tradeNo, lock)
|
||||
}
|
||||
}
|
||||
lock.(*sync.Mutex).Lock()
|
||||
}
|
||||
|
||||
// UnlockOrder 释放给定订单号的锁
|
||||
func UnlockOrder(tradeNo string) {
|
||||
lock, ok := orderLocks.Load(tradeNo)
|
||||
if ok {
|
||||
lock.(*sync.Mutex).Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func EpayNotify(c *gin.Context) {
|
||||
params := lo.Reduce(lo.Keys(c.Request.URL.Query()), func(r map[string]string, t string, i int) map[string]string {
|
||||
r[t] = c.Request.URL.Query().Get(t)
|
||||
return r
|
||||
}, map[string]string{})
|
||||
client := GetEpayClient()
|
||||
if client == nil {
|
||||
log.Println("易支付回调失败 未找到配置信息")
|
||||
_, err := c.Writer.Write([]byte("fail"))
|
||||
if err != nil {
|
||||
log.Println("易支付回调写入失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
verifyInfo, err := client.Verify(params)
|
||||
if err == nil && verifyInfo.VerifyStatus {
|
||||
_, err := c.Writer.Write([]byte("success"))
|
||||
if err != nil {
|
||||
log.Println("易支付回调写入失败")
|
||||
}
|
||||
} else {
|
||||
_, err := c.Writer.Write([]byte("fail"))
|
||||
if err != nil {
|
||||
log.Println("易支付回调写入失败")
|
||||
}
|
||||
log.Println("易支付回调签名验证失败")
|
||||
return
|
||||
}
|
||||
|
||||
if verifyInfo.TradeStatus == epay.StatusTradeSuccess {
|
||||
log.Println(verifyInfo)
|
||||
LockOrder(verifyInfo.ServiceTradeNo)
|
||||
defer UnlockOrder(verifyInfo.ServiceTradeNo)
|
||||
topUp := model.GetTopUpByTradeNo(verifyInfo.ServiceTradeNo)
|
||||
if topUp == nil {
|
||||
log.Printf("易支付回调未找到订单: %v", verifyInfo)
|
||||
return
|
||||
}
|
||||
if topUp.Status == "pending" {
|
||||
topUp.Status = "success"
|
||||
err := topUp.Update()
|
||||
if err != nil {
|
||||
log.Printf("易支付回调更新订单失败: %v", topUp)
|
||||
return
|
||||
}
|
||||
//user, _ := model.GetUserById(topUp.UserId, false)
|
||||
//user.Quota += topUp.Amount * 500000
|
||||
err = model.IncreaseUserQuota(topUp.UserId, topUp.Amount*500000)
|
||||
if err != nil {
|
||||
log.Printf("易支付回调更新用户失败: %v", topUp)
|
||||
return
|
||||
}
|
||||
log.Printf("易支付回调更新用户成功 %v", topUp)
|
||||
model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", common.LogQuota(topUp.Amount*500000), topUp.Money))
|
||||
}
|
||||
} else {
|
||||
log.Printf("易支付异常回调: %v", verifyInfo)
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"message": "success",
|
||||
"data": gin.H{
|
||||
"payLink": payLink,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func RequestAmount(c *gin.Context) {
|
||||
@@ -206,12 +131,23 @@ func RequestAmount(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "error", "data": "参数错误"})
|
||||
return
|
||||
}
|
||||
if !common.PaymentEnabled {
|
||||
c.JSON(200, gin.H{"message": "error", "data": "管理员未开启在线支付"})
|
||||
return
|
||||
}
|
||||
if req.Amount < common.MinTopUp {
|
||||
c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", common.MinTopUp)})
|
||||
return
|
||||
}
|
||||
id := c.GetInt("id")
|
||||
user, _ := model.GetUserById(id, false)
|
||||
payMoney := GetAmount(float64(req.Amount), *user)
|
||||
c.JSON(200, gin.H{"message": "success", "data": strconv.FormatFloat(payMoney, 'f', 2, 64)})
|
||||
payMoney := GetPayAmount(float64(req.Amount))
|
||||
chargedMoney := GetChargedAmount(float64(req.Amount), *user)
|
||||
c.JSON(200, gin.H{
|
||||
"message": "success",
|
||||
"data": gin.H{
|
||||
"payAmount": strconv.FormatFloat(payMoney, 'f', 2, 64),
|
||||
"chargedAmount": strconv.FormatFloat(chargedMoney, 'f', 2, 64),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@@ -59,6 +59,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/stripe/stripe-go/v76 v76.21.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
4
go.sum
4
go.sum
@@ -153,6 +153,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stripe/stripe-go/v76 v76.21.0 h1:O3GHImHS4oUI3qWMOClHN3zAQF5/oswS/NB7leV1fsU=
|
||||
github.com/stripe/stripe-go/v76 v76.21.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -178,6 +180,7 @@ golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
@@ -185,6 +188,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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=
|
||||
|
@@ -58,11 +58,11 @@ func InitOptionMap() {
|
||||
common.OptionMap["SystemName"] = common.SystemName
|
||||
common.OptionMap["Logo"] = common.Logo
|
||||
common.OptionMap["ServerAddress"] = ""
|
||||
common.OptionMap["PayAddress"] = ""
|
||||
common.OptionMap["CustomCallbackAddress"] = ""
|
||||
common.OptionMap["EpayId"] = ""
|
||||
common.OptionMap["EpayKey"] = ""
|
||||
common.OptionMap["Price"] = strconv.FormatFloat(common.Price, 'f', -1, 64)
|
||||
common.OptionMap["StripeApiSecret"] = common.StripeApiSecret
|
||||
common.OptionMap["StripeWebhookSecret"] = common.StripeWebhookSecret
|
||||
common.OptionMap["StripePriceId"] = common.StripePriceId
|
||||
common.OptionMap["PaymentEnabled"] = strconv.FormatBool(common.PaymentEnabled)
|
||||
common.OptionMap["StripeUnitPrice"] = strconv.FormatFloat(common.StripeUnitPrice, 'f', -1, 64)
|
||||
common.OptionMap["MinTopUp"] = strconv.Itoa(common.MinTopUp)
|
||||
common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
|
||||
common.OptionMap["GitHubClientId"] = ""
|
||||
@@ -223,16 +223,16 @@ func updateOptionMap(key string, value string) (err error) {
|
||||
common.SMTPToken = value
|
||||
case "ServerAddress":
|
||||
common.ServerAddress = value
|
||||
case "PayAddress":
|
||||
common.PayAddress = value
|
||||
case "CustomCallbackAddress":
|
||||
common.CustomCallbackAddress = value
|
||||
case "EpayId":
|
||||
common.EpayId = value
|
||||
case "EpayKey":
|
||||
common.EpayKey = value
|
||||
case "Price":
|
||||
common.Price, _ = strconv.ParseFloat(value, 64)
|
||||
case "StripeApiSecret":
|
||||
common.StripeApiSecret = value
|
||||
case "StripeWebhookSecret":
|
||||
common.StripeWebhookSecret = value
|
||||
case "StripePriceId":
|
||||
common.StripePriceId = value
|
||||
case "PaymentEnabled":
|
||||
common.PaymentEnabled, _ = strconv.ParseBool(value)
|
||||
case "StripeUnitPrice":
|
||||
common.StripeUnitPrice, _ = strconv.ParseFloat(value, 64)
|
||||
case "MinTopUp":
|
||||
common.MinTopUp, _ = strconv.Atoi(value)
|
||||
case "TopupGroupRatio":
|
||||
|
@@ -1,13 +1,21 @@
|
||||
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 int `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 int `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) (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).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("使用在线充值成功,充值金额: %v,支付金额:%d", common.LogQuotaF(quota), topUp.Amount))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -31,13 +31,14 @@ func SetApiRouter(router *gin.Engine) {
|
||||
apiRouter.GET("/oauth/telegram/login", middleware.CriticalRateLimit(), controller.TelegramLogin)
|
||||
apiRouter.GET("/oauth/telegram/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.TelegramBind)
|
||||
|
||||
apiRouter.POST("/stripe/webhook", controller.StripeWebhook)
|
||||
|
||||
userRoute := apiRouter.Group("/user")
|
||||
{
|
||||
userRoute.POST("/register", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Register)
|
||||
userRoute.POST("/login", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.Login)
|
||||
//userRoute.POST("/tokenlog", middleware.CriticalRateLimit(), controller.TokenLog)
|
||||
userRoute.GET("/logout", controller.Logout)
|
||||
userRoute.GET("/epay/notify", controller.EpayNotify)
|
||||
|
||||
selfRoute := userRoute.Group("/")
|
||||
selfRoute.Use(middleware.UserAuth())
|
||||
@@ -48,8 +49,8 @@ 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.RequestPayLink)
|
||||
selfRoute.POST("/amount", controller.RequestAmount)
|
||||
selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
|
||||
}
|
||||
|
@@ -1,10 +0,0 @@
|
||||
package service
|
||||
|
||||
import "one-api/common"
|
||||
|
||||
func GetCallbackAddress() string {
|
||||
if common.CustomCallbackAddress == "" {
|
||||
return common.ServerAddress
|
||||
}
|
||||
return common.CustomCallbackAddress
|
||||
}
|
@@ -21,13 +21,13 @@ const SystemSetting = () => {
|
||||
SMTPFrom: '',
|
||||
SMTPToken: '',
|
||||
ServerAddress: '',
|
||||
EpayId: '',
|
||||
EpayKey: '',
|
||||
Price: 7.3,
|
||||
MinTopUp: 1,
|
||||
StripeApiSecret: '',
|
||||
StripeWebhookSecret: '',
|
||||
StripePriceId: '',
|
||||
PaymentEnabled: false,
|
||||
StripeUnitPrice: 8.0,
|
||||
MinTopUp: 5,
|
||||
TopupGroupRatio: '',
|
||||
PayAddress: '',
|
||||
CustomCallbackAddress: '',
|
||||
Footer: '',
|
||||
WeChatAuthEnabled: '',
|
||||
WeChatServerAddress: '',
|
||||
@@ -92,6 +92,7 @@ const SystemSetting = () => {
|
||||
case 'TurnstileCheckEnabled':
|
||||
case 'EmailDomainRestrictionEnabled':
|
||||
case 'RegisterEnabled':
|
||||
case 'PaymentEnabled':
|
||||
value = inputs[key] === 'true' ? 'false' : 'true';
|
||||
break;
|
||||
default:
|
||||
@@ -106,9 +107,6 @@ const SystemSetting = () => {
|
||||
if (key === 'EmailDomainWhitelist') {
|
||||
value = value.split(',');
|
||||
}
|
||||
if (key === 'Price') {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
setInputs((inputs) => ({
|
||||
...inputs, [key]: value
|
||||
}));
|
||||
@@ -128,10 +126,11 @@ const SystemSetting = () => {
|
||||
name === 'Notice' ||
|
||||
name.startsWith('SMTP') ||
|
||||
name === 'ServerAddress' ||
|
||||
name === 'EpayId' ||
|
||||
name === 'EpayKey' ||
|
||||
name === 'Price' ||
|
||||
name === 'PayAddress' ||
|
||||
name === 'StripeApiSecret' ||
|
||||
name === 'StripeWebhookSecret' ||
|
||||
name === 'StripePriceId' ||
|
||||
name === 'StripeUnitPrice' ||
|
||||
name === 'MinTopUp' ||
|
||||
name === 'GitHubClientId' ||
|
||||
name === 'GitHubClientSecret' ||
|
||||
name === 'LinuxDoClientId' ||
|
||||
@@ -158,7 +157,7 @@ const SystemSetting = () => {
|
||||
await updateOption('ServerAddress', ServerAddress);
|
||||
};
|
||||
|
||||
const submitPayAddress = async () => {
|
||||
const submitPaymentConfig = async () => {
|
||||
if (inputs.ServerAddress === '') {
|
||||
showError('请先填写服务器地址');
|
||||
return;
|
||||
@@ -170,15 +169,30 @@ const SystemSetting = () => {
|
||||
}
|
||||
await updateOption('TopupGroupRatio', inputs.TopupGroupRatio);
|
||||
}
|
||||
let PayAddress = removeTrailingSlash(inputs.PayAddress);
|
||||
await updateOption('PayAddress', PayAddress);
|
||||
if (inputs.EpayId !== '') {
|
||||
await updateOption('EpayId', inputs.EpayId);
|
||||
let stripeApiSecret = removeTrailingSlash(inputs.StripeApiSecret);
|
||||
if (stripeApiSecret && !stripeApiSecret.startsWith("sk_")) {
|
||||
showError('输入了无效的Stripe API密钥');
|
||||
return;
|
||||
}
|
||||
if (inputs.EpayKey !== '') {
|
||||
await updateOption('EpayKey', inputs.EpayKey);
|
||||
stripeApiSecret && await updateOption('StripeApiSecret', stripeApiSecret);
|
||||
|
||||
let stripeWebhookSecret = removeTrailingSlash(inputs.StripeWebhookSecret);
|
||||
if (stripeWebhookSecret && !stripeWebhookSecret.startsWith("whsec_")) {
|
||||
showError('输入了无效的Stripe Webhook签名密钥');
|
||||
return;
|
||||
}
|
||||
await updateOption('Price', '' + inputs.Price);
|
||||
stripeWebhookSecret && await updateOption('StripeWebhookSecret', stripeWebhookSecret);
|
||||
|
||||
let stripePriceId = removeTrailingSlash(inputs.StripePriceId);
|
||||
if (stripePriceId && !stripePriceId.startsWith("price_")) {
|
||||
showError('输入了无效的Stripe 物品价格ID');
|
||||
return;
|
||||
}
|
||||
await updateOption('StripePriceId', stripePriceId);
|
||||
|
||||
await updateOption('PaymentEnable', inputs.PaymentEnabled);
|
||||
await updateOption('StripeUnitPrice', inputs.StripeUnitPrice);
|
||||
await updateOption('MinTopUp', inputs.MinTopUp);
|
||||
};
|
||||
|
||||
const submitSMTP = async () => {
|
||||
@@ -318,54 +332,66 @@ const SystemSetting = () => {
|
||||
更新服务器地址
|
||||
</Form.Button>
|
||||
<Divider />
|
||||
<Header as="h3">支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)</Header>
|
||||
<Header as="h3">
|
||||
支付设置(当前仅支持Stripe Checkout)
|
||||
<Header.Subheader>
|
||||
密钥、Webhook 等设置请
|
||||
<a href="https://dashboard.stripe.com/developers" target="_blank" rel="noreferrer">
|
||||
点击此处
|
||||
</a>
|
||||
进行设置,最好先在
|
||||
<a href="https://dashboard.stripe.com/test/developers" target="_blank" rel="noreferrer">
|
||||
测试环境
|
||||
</a>
|
||||
进行测试
|
||||
</Header.Subheader>
|
||||
</Header>
|
||||
<Message>
|
||||
Webhook 填:
|
||||
<code>{`${inputs.ServerAddress}/api/stripe/webhook`}</code>
|
||||
,需要包含事件:<code>checkout.session.completed</code> 和 <code>checkout.session.expired</code>
|
||||
</Message>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Input
|
||||
label="支付地址,不填写则不启用在线支付"
|
||||
placeholder="例如:https://yourdomain.com"
|
||||
value={inputs.PayAddress}
|
||||
name="PayAddress"
|
||||
label="API密钥"
|
||||
placeholder="sk_xxx的Stripe密钥,敏感信息不显示"
|
||||
value={inputs.StripeApiSecret}
|
||||
name="StripeApiSecret"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Input
|
||||
label="易支付商户ID"
|
||||
placeholder="例如:0001"
|
||||
value={inputs.EpayId}
|
||||
name="EpayId"
|
||||
label="Webhook签名密钥"
|
||||
placeholder="whsec_xxx的Webhook签名密钥,敏感信息不显示"
|
||||
value={inputs.StripeWebhookSecret}
|
||||
name="StripeWebhookSecret"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Input
|
||||
label="易支付商户密钥"
|
||||
placeholder="例如:dejhfueqhujasjmndbjkqaw"
|
||||
value={inputs.EpayKey}
|
||||
name="EpayKey"
|
||||
label="商品价格ID"
|
||||
placeholder="price_xxx的商品价格ID,新建产品后可获得"
|
||||
value={inputs.StripePriceId}
|
||||
name="StripePriceId"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Input
|
||||
label="回调地址,不填写则使用上方服务器地址作为回调地址"
|
||||
placeholder="例如:https://yourdomain.com"
|
||||
value={inputs.CustomCallbackAddress}
|
||||
name="CustomCallbackAddress"
|
||||
onChange={handleInputChange}
|
||||
label="商品单价(元)"
|
||||
placeholder="商品的人民币价格"
|
||||
value={inputs.StripeUnitPrice}
|
||||
name="StripeUnitPrice"
|
||||
type={"number"}
|
||||
min={0}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Input
|
||||
label="充值价格(x元/美金)"
|
||||
placeholder="例如:7,就是7元/美金"
|
||||
value={inputs.Price}
|
||||
name="Price"
|
||||
min={0}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<Form.Input
|
||||
label="最低充值数量"
|
||||
placeholder="例如:2,就是最低充值2$"
|
||||
value={inputs.MinTopUp}
|
||||
name="MinTopUp"
|
||||
min={1}
|
||||
onChange={handleInputChange}
|
||||
label="最低充值数量"
|
||||
placeholder="例如:2,就是最低充值2件商品"
|
||||
value={inputs.MinTopUp}
|
||||
name="MinTopUp"
|
||||
type={"number"}
|
||||
min={1}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group widths="equal">
|
||||
@@ -379,9 +405,17 @@ const SystemSetting = () => {
|
||||
placeholder="为一个 JSON 文本,键为组名称,值为倍率"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitPayAddress}>
|
||||
更新支付设置
|
||||
</Form.Button>
|
||||
<Form.Group inline>
|
||||
<Form.Button onClick={submitPaymentConfig}>
|
||||
更新支付设置
|
||||
</Form.Button>
|
||||
<Form.Checkbox
|
||||
checked={inputs.PaymentEnabled === 'true'}
|
||||
label="开启在线支付"
|
||||
name="PaymentEnabled"
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Divider />
|
||||
<Header as="h3">配置登录注册</Header>
|
||||
<Form.Group inline>
|
||||
|
@@ -126,6 +126,10 @@ export function openPage(url) {
|
||||
}
|
||||
|
||||
export function removeTrailingSlash(url) {
|
||||
if (!url) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (url.endsWith('/')) {
|
||||
return url.slice(0, -1);
|
||||
} else {
|
||||
|
@@ -11,18 +11,20 @@ const TopUp = () => {
|
||||
const [topUpCode, setTopUpCode] = useState('');
|
||||
const [topUpCount, setTopUpCount] = useState(10);
|
||||
const [minTopupCount, setMinTopUpCount] = useState(1);
|
||||
const [amount, setAmount] = useState(0.0);
|
||||
const [payAmount, setPayAmount] = useState(0.0);
|
||||
const [chargedAmount, setChargedAmount] = useState(0.0);
|
||||
const [minTopUp, setMinTopUp] = useState(1);
|
||||
const [topUpLink, setTopUpLink] = useState('');
|
||||
const [enableOnlineTopUp, setEnableOnlineTopUp] = useState(false);
|
||||
const [paymentEnabled, setPaymentEnabled] = useState(false);
|
||||
const [userQuota, setUserQuota] = useState(0);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isPaying, setIsPaying] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [payWay, setPayWay] = useState('');
|
||||
|
||||
const topUp = async () => {
|
||||
if (redemptionCode === '') {
|
||||
showInfo('请输入兑换码!')
|
||||
showError('请输入兑换码!')
|
||||
return;
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
@@ -57,15 +59,19 @@ const TopUp = () => {
|
||||
};
|
||||
|
||||
const preTopUp = async (payment) => {
|
||||
if (!enableOnlineTopUp) {
|
||||
if (!paymentEnabled) {
|
||||
showError('管理员未开启在线充值!');
|
||||
return;
|
||||
}
|
||||
if (amount === 0) {
|
||||
if (!Number.isInteger(Number(topUpCount))) {
|
||||
showError('充值数量必须是整数!');
|
||||
return;
|
||||
}
|
||||
if (payAmount === 0) {
|
||||
await getAmount();
|
||||
}
|
||||
if (topUpCount < minTopUp) {
|
||||
showInfo('充值数量不能小于' + minTopUp);
|
||||
showError('充值数量不能小于' + minTopUp);
|
||||
return;
|
||||
}
|
||||
setPayWay(payment)
|
||||
@@ -73,15 +79,16 @@ const TopUp = () => {
|
||||
}
|
||||
|
||||
const onlineTopUp = async () => {
|
||||
if (amount === 0) {
|
||||
if (payAmount === 0) {
|
||||
await getAmount();
|
||||
}
|
||||
if (topUpCount < minTopUp) {
|
||||
showInfo('充值数量不能小于' + minTopUp);
|
||||
showError('充值数量不能小于' + minTopUp);
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
try {
|
||||
setIsPaying(true)
|
||||
const res = await API.post('/api/user/pay', {
|
||||
amount: parseInt(topUpCount),
|
||||
top_up_code: topUpCode,
|
||||
@@ -91,33 +98,13 @@ const TopUp = () => {
|
||||
const {message, data} = res.data;
|
||||
// showInfo(message);
|
||||
if (message === 'success') {
|
||||
|
||||
let params = data
|
||||
let url = res.data.url
|
||||
let form = document.createElement('form')
|
||||
form.action = url
|
||||
form.method = 'POST'
|
||||
// 判断是否为safari浏览器
|
||||
let isSafari = navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") < 1;
|
||||
if (!isSafari) {
|
||||
form.target = '_blank'
|
||||
}
|
||||
for (let key in params) {
|
||||
let input = document.createElement('input')
|
||||
input.type = 'hidden'
|
||||
input.name = key
|
||||
input.value = params[key]
|
||||
form.appendChild(input)
|
||||
}
|
||||
document.body.appendChild(form)
|
||||
form.submit()
|
||||
document.body.removeChild(form)
|
||||
location.href = data.payLink
|
||||
} else {
|
||||
setIsPaying(false)
|
||||
showError(data);
|
||||
// setTopUpCount(parseInt(res.data.count));
|
||||
// setAmount(parseInt(data));
|
||||
}
|
||||
} else {
|
||||
setIsPaying(false)
|
||||
showError(res);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -146,8 +133,8 @@ const TopUp = () => {
|
||||
if (status.min_topup) {
|
||||
setMinTopUp(status.min_topup);
|
||||
}
|
||||
if (status.enable_online_topup) {
|
||||
setEnableOnlineTopUp(status.enable_online_topup);
|
||||
if (status.payment_enabled) {
|
||||
setPaymentEnabled(status.payment_enabled);
|
||||
}
|
||||
}
|
||||
getUserQuota().then();
|
||||
@@ -155,7 +142,7 @@ const TopUp = () => {
|
||||
|
||||
const renderAmount = () => {
|
||||
// console.log(amount);
|
||||
return amount + '元';
|
||||
return payAmount + '元';
|
||||
}
|
||||
|
||||
const getAmount = async (value) => {
|
||||
@@ -171,7 +158,8 @@ const TopUp = () => {
|
||||
const {message, data} = res.data;
|
||||
// showInfo(message);
|
||||
if (message === 'success') {
|
||||
setAmount(parseFloat(data));
|
||||
setPayAmount(parseFloat(data.payAmount));
|
||||
setChargedAmount(parseFloat(data.chargedAmount));
|
||||
} else {
|
||||
showError(data);
|
||||
// setTopUpCount(parseInt(res.data.count));
|
||||
@@ -206,7 +194,7 @@ const TopUp = () => {
|
||||
size={'small'}
|
||||
centered={true}
|
||||
>
|
||||
<p>充值数量:{topUpCount}$</p>
|
||||
<p>充值数量:{topUpCount}$(实到:{chargedAmount}$)</p>
|
||||
<p>实付金额:{renderAmount()}</p>
|
||||
<p>是否确认充值?</p>
|
||||
</Modal>
|
||||
@@ -244,54 +232,50 @@ const TopUp = () => {
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
<div style={{marginTop: 20}}>
|
||||
<Divider>
|
||||
在线充值
|
||||
</Divider>
|
||||
<Form>
|
||||
<Form.Input
|
||||
disabled={!enableOnlineTopUp}
|
||||
field={'redemptionCount'}
|
||||
label={'实付金额:' + renderAmount()}
|
||||
placeholder={'充值数量,最低' + minTopUp + '$'}
|
||||
name='redemptionCount'
|
||||
type={'number'}
|
||||
value={topUpCount}
|
||||
suffix={'$'}
|
||||
min={minTopUp}
|
||||
defaultValue={minTopUp}
|
||||
max={100000}
|
||||
onChange={async (value) => {
|
||||
if (value < 1) {
|
||||
value = 1;
|
||||
}
|
||||
if (value > 100000) {
|
||||
value = 100000;
|
||||
}
|
||||
setTopUpCount(value);
|
||||
await getAmount(value);
|
||||
}}
|
||||
/>
|
||||
<Space>
|
||||
<Button type={'primary'} theme={'solid'} onClick={
|
||||
async () => {
|
||||
preTopUp('zfb')
|
||||
}
|
||||
}>
|
||||
支付宝
|
||||
</Button>
|
||||
<Button style={{backgroundColor: 'rgba(var(--semi-green-5), 1)'}}
|
||||
type={'primary'}
|
||||
theme={'solid'} onClick={
|
||||
async () => {
|
||||
preTopUp('wx')
|
||||
}
|
||||
}>
|
||||
微信
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
{paymentEnabled ?
|
||||
<div style={{marginTop: 20}}>
|
||||
<Divider>
|
||||
在线充值
|
||||
</Divider>
|
||||
<Form>
|
||||
<Form.Input
|
||||
disabled={!paymentEnabled}
|
||||
field={'redemptionCount'}
|
||||
label={'实付金额:' + renderAmount()}
|
||||
placeholder={'充值数量,必须整数,最低' + minTopUp + '$'}
|
||||
name='redemptionCount'
|
||||
type={'number'}
|
||||
value={topUpCount}
|
||||
suffix={'$'}
|
||||
min={minTopUp}
|
||||
defaultValue={minTopUp}
|
||||
max={100000}
|
||||
onChange={async (value) => {
|
||||
if (value < 1) {
|
||||
value = 1;
|
||||
}
|
||||
if (value > 100000) {
|
||||
value = 100000;
|
||||
}
|
||||
setTopUpCount(value);
|
||||
await getAmount(value);
|
||||
}}
|
||||
/>
|
||||
<Space>
|
||||
<Button style={{backgroundColor: '#2e75cd'}}
|
||||
type={'primary'}
|
||||
disabled={isPaying}
|
||||
theme={'solid'} onClick={
|
||||
async () => {
|
||||
preTopUp('stripe')
|
||||
}
|
||||
}>
|
||||
{isPaying ? '支付中...' : '去支付'}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div> : <></>
|
||||
}
|
||||
{/*<div style={{ display: 'flex', justifyContent: 'right' }}>*/}
|
||||
{/* <Text>*/}
|
||||
{/* <Link onClick={*/}
|
||||
|
Reference in New Issue
Block a user