From 6fc114ec322d4c0a4e3e08d6844fec31efb47ecf Mon Sep 17 00:00:00 2001 From: xiezhouwei Date: Mon, 8 Jun 2026 09:01:33 +0800 Subject: [PATCH] feat: add Alipay and WeChat Pay payment gateway support - Add Alipay payment backend: RSA2 signing/verification, async notify, sync return - Add WeChat Pay backend: APIv3 signing, AES-GCM decryption, H5 pay, async notify - Add payment setting UI components for Alipay and WeChat Pay - Register config keys (24 items) in OptionMap for both gateways - Add API routes: /api/user/alipay/pay, /api/user/wechatpay/pay, /api/alipay/notify, /api/alipay/return, /api/wechatpay/notify - Add enable_alipay_topup/enable_wechatpay_topup to /api/status endpoint - Add i18n translations for all new UI strings (11 locales) - Fix: data is not defined ReferenceError in topup/index.jsx useEffect - Fix: unused imports in controller/topup_wechat.go - Update .gitignore for build artifacts --- .gitignore | 9 + controller/misc.go | 2 + controller/topup.go | 50 +- controller/topup_alipay.go | 463 ++++++++++++++++ controller/topup_wechat.go | 515 ++++++++++++++++++ model/option.go | 77 ++- router/api-router.go | 8 +- setting/payment_alipay.go | 16 + setting/payment_wechat.go | 19 + .../components/settings/PaymentSetting.jsx | 144 +++-- web/src/components/topup/index.jsx | 46 +- web/src/i18n/locales/en.json | 90 ++- web/src/i18n/locales/fr.json | 91 +++- web/src/i18n/locales/id.json | 91 +++- web/src/i18n/locales/ja.json | 91 +++- web/src/i18n/locales/ms.json | 91 +++- web/src/i18n/locales/ru.json | 91 +++- web/src/i18n/locales/sw.json | 91 +++- web/src/i18n/locales/th.json | 91 +++- web/src/i18n/locales/vi.json | 91 +++- web/src/i18n/locales/zh-CN.json | 91 +++- web/src/i18n/locales/zh-TW.json | 89 ++- .../Operation/SettingsSidebarModulesAdmin.jsx | 6 +- .../Payment/SettingsPaymentGatewayAlipay.jsx | 308 +++++++++++ .../Payment/SettingsPaymentGatewayWechat.jsx | 351 ++++++++++++ 25 files changed, 2924 insertions(+), 88 deletions(-) create mode 100644 controller/topup_alipay.go create mode 100644 controller/topup_wechat.go create mode 100644 setting/payment_alipay.go create mode 100644 setting/payment_wechat.go create mode 100644 web/src/pages/Setting/Payment/SettingsPaymentGatewayAlipay.jsx create mode 100644 web/src/pages/Setting/Payment/SettingsPaymentGatewayWechat.jsx diff --git a/.gitignore b/.gitignore index 62444e4..dec0d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,12 @@ redis_data/ .gomodcache/ .gocache-temp .gopath + +# Build artifacts +*.exe + +# Test artifacts +.playwright-cli/ + +# Misc + diff --git a/controller/misc.go b/controller/misc.go index 6d40272..5f2987f 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -106,6 +106,8 @@ func GetStatus(c *gin.Context) { "usd_exchange_rate": operation_setting.USDExchangeRate, "price": operation_setting.Price, "stripe_unit_price": setting.StripeUnitPrice, + "enable_alipay_topup": setting.AlipayEnabled && setting.AlipayAppId != "" && setting.AlipayPrivateKey != "", + "enable_wechatpay_topup": setting.WechatPayEnabled && setting.WechatPayAppId != "" && setting.WechatPayMchId != "" && setting.WechatPayApiV3Key != "", // 面板启用开关 "api_info_enabled": cs.ApiInfoEnabled, diff --git a/controller/topup.go b/controller/topup.go index a35de0d..3aece5e 100644 --- a/controller/topup.go +++ b/controller/topup.go @@ -87,6 +87,46 @@ func GetTopUpInfo(c *gin.Context) { } } + // If Alipay is enabled, add it to payment methods + if setting.AlipayEnabled && setting.AlipayAppId != "" && setting.AlipayPrivateKey != "" { + hasAlipay := false + for _, method := range payMethods { + if method["type"] == "alipay" { + hasAlipay = true + break + } + } + if !hasAlipay { + alipayMethod := map[string]string{ + "name": "Alipay (支付宝)", + "type": "alipay", + "color": "rgba(var(--semi-blue-5), 1)", + "min_topup": strconv.Itoa(setting.AlipayMinTopUp), + } + payMethods = append(payMethods, alipayMethod) + } + } + + // If WeChat Pay is enabled, add it to payment methods + if setting.WechatPayEnabled && setting.WechatPayAppId != "" && setting.WechatPayMchId != "" { + hasWechat := false + for _, method := range payMethods { + if method["type"] == "wxpay" { + hasWechat = true + break + } + } + if !hasWechat { + wechatMethod := map[string]string{ + "name": "WeChat Pay (微信支付)", + "type": "wxpay", + "color": "rgba(var(--semi-green-5), 1)", + "min_topup": strconv.Itoa(setting.WechatPayMinTopUp), + } + payMethods = append(payMethods, wechatMethod) + } + } + data := gin.H{ "enable_online_topup": (operation_setting.OnlinePayProvider == "yipay" && (operation_setting.YipayRequestURL != "" || operation_setting.PayAddress != "") && @@ -97,9 +137,13 @@ func GetTopUpInfo(c *gin.Context) { operation_setting.PayAddress != "" && operation_setting.EpayId != "" && operation_setting.EpayKey != ""), - "enable_stripe_topup": setting.StripeApiSecret != "" && setting.StripeWebhookSecret != "" && setting.StripePriceId != "", - "enable_creem_topup": setting.CreemApiKey != "" && setting.CreemProducts != "[]", - "enable_waffo_topup": enableWaffo, + "enable_stripe_topup": setting.StripeApiSecret != "" && setting.StripeWebhookSecret != "" && setting.StripePriceId != "", + "enable_creem_topup": setting.CreemApiKey != "" && setting.CreemProducts != "[]", + "enable_waffo_topup": enableWaffo, + "enable_alipay_topup": setting.AlipayEnabled && setting.AlipayAppId != "" && setting.AlipayPrivateKey != "", + "enable_wechatpay_topup": setting.WechatPayEnabled && setting.WechatPayAppId != "" && setting.WechatPayMchId != "" && setting.WechatPayApiV3Key != "", + "alipay_min_topup": setting.AlipayMinTopUp, + "wechatpay_min_topup": setting.WechatPayMinTopUp, "waffo_pay_methods": func() interface{} { if enableWaffo { return setting.GetWaffoPayMethods() diff --git a/controller/topup_alipay.go b/controller/topup_alipay.go new file mode 100644 index 0000000..ba0b719 --- /dev/null +++ b/controller/topup_alipay.go @@ -0,0 +1,463 @@ +package controller + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io" + "log" + "net/http" + "net/url" + "sort" + "strings" + "time" + + "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/logger" + "github.com/QuantumNous/new-api/model" + "github.com/QuantumNous/new-api/service" + "github.com/QuantumNous/new-api/setting" + "github.com/QuantumNous/new-api/setting/operation_setting" + "github.com/QuantumNous/new-api/setting/system_setting" + + "github.com/gin-gonic/gin" + "github.com/shopspring/decimal" +) + +const ( + PaymentMethodAlipay = "alipay" +) + +var alipayAdaptor = &AlipayAdaptor{} + +// AlipayPayRequest represents a payment request for Alipay checkout. +type AlipayPayRequest struct { + Amount int64 `json:"amount"` + PaymentMethod string `json:"payment_method"` +} + +type AlipayAdaptor struct{} + +func getAlipayAppId() string { + if setting.AlipaySandbox && setting.AlipaySandboxAppId != "" { + return setting.AlipaySandboxAppId + } + return setting.AlipayAppId +} + +func getAlipayPrivateKey() string { + if setting.AlipaySandbox && setting.AlipaySandboxPrivateKey != "" { + return setting.AlipaySandboxPrivateKey + } + return setting.AlipayPrivateKey +} + +func getAlipayPublicKey() string { + return setting.AlipayPublicKey +} + +func getAlipayGatewayURL() string { + if setting.AlipaySandbox { + return "https://openapi-sandbox.dl.alipaydev.com/gateway.do" + } + return "https://openapi.alipay.com/gateway.do" +} + +func getAlipayMinTopUp() int64 { + minTopup := setting.AlipayMinTopUp + if operation_setting.GetQuotaDisplayType() == operation_setting.QuotaDisplayTypeTokens { + minTopup = minTopup * int(common.QuotaPerUnit) + } + return int64(minTopup) +} + +func getAlipayPayMoney(amount float64, group string) float64 { + originalAmount := amount + if operation_setting.GetQuotaDisplayType() == operation_setting.QuotaDisplayTypeTokens { + amount = amount / common.QuotaPerUnit + } + topupGroupRatio := common.GetTopupGroupRatio(group) + if topupGroupRatio == 0 { + topupGroupRatio = 1 + } + discount := 1.0 + if ds, ok := operation_setting.GetPaymentSetting().AmountDiscount[int(originalAmount)]; ok { + if ds > 0 { + discount = ds + } + } + return amount * setting.AlipayUnitPrice * topupGroupRatio * discount +} + +// alipaySign generates RSA2 signature for Alipay params +func alipaySign(params map[string]string, privateKey string) (string, error) { + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + var buf strings.Builder + for i, k := range keys { + v := params[k] + if v == "" { + continue + } + if i > 0 { + buf.WriteString("&") + } + buf.WriteString(k) + buf.WriteString("=") + buf.WriteString(v) + } + + signStr := buf.String() + + block, _ := pem.Decode([]byte(privateKey)) + if block == nil { + return "", fmt.Errorf("failed to decode private key PEM") + } + + priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + priv, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("failed to parse private key: %v", err) + } + } + + rsaKey, ok := priv.(*rsa.PrivateKey) + if !ok { + return "", fmt.Errorf("private key is not RSA key") + } + + hashed := sha256.Sum256([]byte(signStr)) + signature, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, crypto.SHA256, hashed[:]) + if err != nil { + return "", fmt.Errorf("sign failed: %v", err) + } + + return base64.StdEncoding.EncodeToString(signature), nil +} + +// alipayVerify verifies Alipay notification signature +func alipayVerify(params map[string]string, publicKey string) bool { + sign, ok := params["sign"] + if !ok || sign == "" { + return false + } + + keys := make([]string, 0, len(params)) + for k := range params { + if k == "sign" || k == "sign_type" { + continue + } + keys = append(keys, k) + } + sort.Strings(keys) + + var buf strings.Builder + for i, k := range keys { + v := params[k] + if v == "" { + continue + } + if i > 0 { + buf.WriteString("&") + } + buf.WriteString(k) + buf.WriteString("=") + buf.WriteString(v) + } + + signStr := buf.String() + + sigBytes, err := base64.StdEncoding.DecodeString(sign) + if err != nil { + log.Printf("Alipay verify: base64 decode sign failed: %v", err) + return false + } + + block, _ := pem.Decode([]byte(publicKey)) + if block == nil { + log.Printf("Alipay verify: failed to decode public key PEM") + return false + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Printf("Alipay verify: failed to parse public key: %v", err) + return false + } + + rsaPub, ok := pub.(*rsa.PublicKey) + if !ok { + log.Printf("Alipay verify: public key is not RSA key") + return false + } + + hashed := sha256.Sum256([]byte(signStr)) + err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, hashed[:], sigBytes) + return err == nil +} + +func (a *AlipayAdaptor) RequestPay(c *gin.Context, req *AlipayPayRequest) { + if req.PaymentMethod != PaymentMethodAlipay { + c.JSON(200, gin.H{"message": "error", "data": "不支持的支付渠道"}) + return + } + + minTopup := getAlipayMinTopUp() + if req.Amount < minTopup { + c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", minTopup)}) + return + } + if req.Amount > 100000 { + c.JSON(200, gin.H{"message": "error", "data": "充值数量不能大于 100000"}) + return + } + + id := c.GetInt("id") + user, err := model.GetUserById(id, false) + if err != nil || user == nil { + c.JSON(200, gin.H{"message": "error", "data": "用户不存在"}) + return + } + + group, err := model.GetUserGroup(id, true) + if err != nil { + c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"}) + return + } + + payMoney := getAlipayPayMoney(float64(req.Amount), group) + if payMoney <= 0.01 { + c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"}) + return + } + + tradeNo := fmt.Sprintf("ALI%d%s%d", id, common.GetRandomString(12), time.Now().Unix()) + + topUp := &model.TopUp{ + UserId: id, + Amount: req.Amount, + Money: payMoney, + TradeNo: tradeNo, + PaymentMethod: PaymentMethodAlipay, + CreateTime: time.Now().Unix(), + Status: common.TopUpStatusPending, + } + err = topUp.Insert() + if err != nil { + log.Printf("创建Alipay订单失败: %v", err) + c.JSON(200, gin.H{"message": "error", "data": "创建订单失败"}) + return + } + + payUrl, err := genAlipayPagePayUrl(tradeNo, payMoney, user.Email) + if err != nil { + log.Printf("生成Alipay支付链接失败: %v", err) + c.JSON(200, gin.H{"message": "error", "data": "拉起支付失败"}) + return + } + + log.Printf("Alipay订单创建成功 - 用户ID: %d, 订单号: %s, 金额: %.2f", id, tradeNo, payMoney) + + c.JSON(200, gin.H{ + "message": "success", + "data": gin.H{ + "pay_url": payUrl, + "trade_no": tradeNo, + }, + }) +} + +// genAlipayPagePayUrl generates an Alipay page payment URL +func genAlipayPagePayUrl(tradeNo string, payMoney float64, email string) (string, error) { + appId := getAlipayAppId() + privateKey := getAlipayPrivateKey() + + if appId == "" || privateKey == "" { + return "", fmt.Errorf("Alipay not configured") + } + + callBackAddress := service.GetCallbackAddress() + notifyUrl := strings.TrimRight(callBackAddress, "/") + "/api/alipay/notify" + if setting.AlipayNotifyUrl != "" { + notifyUrl = setting.AlipayNotifyUrl + } + + returnUrl := system_setting.ServerAddress + "/console/log" + if setting.AlipayReturnUrl != "" { + returnUrl = setting.AlipayReturnUrl + } + + amountStr := fmt.Sprintf("%.2f", payMoney) + + params := map[string]string{ + "app_id": appId, + "method": "alipay.trade.page.pay", + "charset": "utf-8", + "sign_type": "RSA2", + "timestamp": time.Now().Format("2006-01-02 15:04:05"), + "version": "1.0", + "notify_url": notifyUrl, + "return_url": returnUrl, + "biz_content": fmt.Sprintf(`{"out_trade_no":"%s","product_code":"FAST_INSTANT_TRADE_PAY","total_amount":"%s","subject":"TokenFactory TopUp"}`, tradeNo, amountStr), + } + + sign, err := alipaySign(params, privateKey) + if err != nil { + return "", err + } + params["sign"] = sign + + gatewayUrl := getAlipayGatewayURL() + queryParams := url.Values{} + for k, v := range params { + queryParams.Set(k, v) + } + + return gatewayUrl + "?" + queryParams.Encode(), nil +} + +// AlipayNotify handles the Alipay async notification +func AlipayNotify(c *gin.Context) { + bodyBytes, err := io.ReadAll(c.Request.Body) + if err != nil { + log.Printf("读取Alipay通知失败: %v", err) + c.String(http.StatusOK, "failure") + return + } + defer c.Request.Body.Close() + + bodyStr := string(bodyBytes) + formValues, err := url.ParseQuery(bodyStr) + if err != nil { + log.Printf("解析Alipay通知参数失败: %v", err) + c.String(http.StatusOK, "failure") + return + } + + params := make(map[string]string) + for k, v := range formValues { + if len(v) > 0 { + params[k] = v[0] + } + } + + log.Printf("收到Alipay异步通知: trade_no=%s, out_trade_no=%s, trade_status=%s", + params["trade_no"], params["out_trade_no"], params["trade_status"]) + + publicKey := getAlipayPublicKey() + if publicKey != "" && !alipayVerify(params, publicKey) { + log.Printf("Alipay通知签名验证失败: %v", params) + c.String(http.StatusOK, "failure") + return + } + + tradeStatus := params["trade_status"] + outTradeNo := params["out_trade_no"] + + if tradeStatus == "TRADE_SUCCESS" || tradeStatus == "TRADE_FINISHED" { + LockOrder(outTradeNo) + defer UnlockOrder(outTradeNo) + + topUp := model.GetTopUpByTradeNo(outTradeNo) + if topUp == nil { + log.Printf("Alipay通知未找到订单: %s", outTradeNo) + c.String(http.StatusOK, "failure") + return + } + + if topUp.Status != common.TopUpStatusPending { + log.Printf("Alipay订单状态错误: %s, 当前状态: %s", outTradeNo, topUp.Status) + c.String(http.StatusOK, "success") + return + } + + dAmount := decimal.NewFromInt(topUp.Amount) + dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit) + quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart()) + + if quotaToAdd <= 0 { + log.Printf("Alipay无效的充值额度: %s", outTradeNo) + c.String(http.StatusOK, "failure") + return + } + + topUp.CompleteTime = common.GetTimestamp() + topUp.Status = common.TopUpStatusSuccess + if err := topUp.Update(); err != nil { + log.Printf("Alipay更新订单失败: %v", err) + c.String(http.StatusOK, "failure") + return + } + + if err := model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true); err != nil { + log.Printf("Alipay更新用户额度失败: %v", err) + c.String(http.StatusOK, "failure") + return + } + + model.RecordLog(topUp.UserId, model.LogTypeTopup, + fmt.Sprintf("使用支付宝充值成功,充值金额: %v,支付金额:%.2f", + logger.LogQuota(quotaToAdd), topUp.Money)) + model.ApplyAffiliateTopupReward(topUp.UserId, quotaToAdd) + + log.Printf("Alipay充值成功 - 订单: %s, 额度: %d", outTradeNo, quotaToAdd) + } + + c.String(http.StatusOK, "success") +} + +// AlipayReturn handles the Alipay synchronous return (page redirect) +func AlipayReturn(c *gin.Context) { + params := make(map[string]string) + for k, v := range c.Request.URL.Query() { + if len(v) > 0 { + params[k] = v[0] + } + } + + log.Printf("Alipay同步返回: out_trade_no=%s, trade_no=%s", params["out_trade_no"], params["trade_no"]) + + publicKey := getAlipayPublicKey() + if publicKey != "" && !alipayVerify(params, publicKey) { + log.Printf("Alipay返回签名验证失败") + c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup") + return + } + + outTradeNo := params["out_trade_no"] + if outTradeNo != "" { + topUp := model.GetTopUpByTradeNo(outTradeNo) + if topUp != nil && topUp.Status == common.TopUpStatusSuccess { + c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/log") + return + } + } + + c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup") +} + +// RequestAlipayPay handles the Alipay payment request from frontend +func RequestAlipayPay(c *gin.Context) { + if !setting.AlipayEnabled { + c.JSON(200, gin.H{"message": "error", "data": "支付宝支付未启用"}) + return + } + + var req AlipayPayRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(200, gin.H{"message": "error", "data": "参数错误"}) + return + } + + alipayAdaptor.RequestPay(c, &req) +} diff --git a/controller/topup_wechat.go b/controller/topup_wechat.go new file mode 100644 index 0000000..9396894 --- /dev/null +++ b/controller/topup_wechat.go @@ -0,0 +1,515 @@ +package controller + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" + + "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/logger" + "github.com/QuantumNous/new-api/model" + "github.com/QuantumNous/new-api/service" + "github.com/QuantumNous/new-api/setting" + "github.com/QuantumNous/new-api/setting/operation_setting" + + "github.com/gin-gonic/gin" + "github.com/shopspring/decimal" +) + +const ( + PaymentMethodWechatPay = "wxpay" +) + +var wechatPayAdaptor = &WechatPayAdaptor{} + +// WechatPayRequest represents a payment request for WeChat Pay. +type WechatPayRequest struct { + Amount int64 `json:"amount"` + PaymentMethod string `json:"payment_method"` +} + +type WechatPayAdaptor struct{} + +func getWechatPayAppId() string { + return setting.WechatPayAppId +} + +func getWechatPayMchId() string { + if setting.WechatPaySandbox && setting.WechatPaySandboxMchId != "" { + return setting.WechatPaySandboxMchId + } + return setting.WechatPayMchId +} + +func getWechatPayApiV3Key() string { + if setting.WechatPaySandbox && setting.WechatPaySandboxApiV3Key != "" { + return setting.WechatPaySandboxApiV3Key + } + return setting.WechatPayApiV3Key +} + +func getWechatPayPrivateKey() string { + if setting.WechatPaySandbox && setting.WechatPaySandboxPrivateKey != "" { + return setting.WechatPaySandboxPrivateKey + } + return setting.WechatPayPrivateKey +} + +func getWechatPayAPIBaseURL() string { + if setting.WechatPaySandbox { + return "https://apihk.mch.weixin.qq.com/sandboxnew" + } + return "https://api.mch.weixin.qq.com" +} + +func getWechatPayMinTopUp() int64 { + minTopup := setting.WechatPayMinTopUp + if operation_setting.GetQuotaDisplayType() == operation_setting.QuotaDisplayTypeTokens { + minTopup = minTopup * int(common.QuotaPerUnit) + } + return int64(minTopup) +} + +func getWechatPayPayMoney(amount float64, group string) float64 { + originalAmount := amount + if operation_setting.GetQuotaDisplayType() == operation_setting.QuotaDisplayTypeTokens { + amount = amount / common.QuotaPerUnit + } + topupGroupRatio := common.GetTopupGroupRatio(group) + if topupGroupRatio == 0 { + topupGroupRatio = 1 + } + discount := 1.0 + if ds, ok := operation_setting.GetPaymentSetting().AmountDiscount[int(originalAmount)]; ok { + if ds > 0 { + discount = ds + } + } + return amount * setting.WechatPayUnitPrice * topupGroupRatio * discount +} + +// wechatPaySign generates the WeChat Pay v3 signature +func wechatPaySign(method string, urlPath string, body string, timestamp string, nonce string, privateKeyStr string) (string, error) { + message := fmt.Sprintf("%s\n%s\n%s\n%s\n", method, urlPath, timestamp, nonce) + if body != "" && body != "{}" { + message = fmt.Sprintf("%s\n%s\n%s\n%s\n", method, urlPath, timestamp, nonce) + body + } else { + message = fmt.Sprintf("%s\n%s\n%s\n%s\n", method, urlPath, timestamp, nonce) + } + + block, _ := pem.Decode([]byte(privateKeyStr)) + if block == nil { + return "", fmt.Errorf("failed to decode private key PEM") + } + + priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + priv, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("failed to parse private key: %v", err) + } + } + + rsaKey, ok := priv.(*rsa.PrivateKey) + if !ok { + return "", fmt.Errorf("private key is not RSA key") + } + + hashed := sha256.Sum256([]byte(message)) + signature, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, crypto.SHA256, hashed[:]) + if err != nil { + return "", fmt.Errorf("sign failed: %v", err) + } + + return base64.StdEncoding.EncodeToString(signature), nil +} + +// wechatPayBuildAuthHeader builds the WeChat Pay v3 Authorization header +func wechatPayBuildAuthHeader(method string, urlPath string, body string) (string, error) { + mchId := getWechatPayMchId() + privateKey := getWechatPayPrivateKey() + certSerial := setting.WechatPayCertSerial + + if mchId == "" || privateKey == "" || certSerial == "" { + return "", fmt.Errorf("WeChat Pay not fully configured") + } + + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + nonce := common.GetRandomString(32) + + sign, err := wechatPaySign(method, urlPath, body, timestamp, nonce, privateKey) + if err != nil { + return "", err + } + + auth := fmt.Sprintf(`WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%s",serial_no="%s",signature="%s"`, + mchId, nonce, timestamp, certSerial, sign) + + return auth, nil +} + +// wechatPayPost sends a POST request to WeChat Pay API v3 +func wechatPayPost(path string, requestBody interface{}) (map[string]interface{}, error) { + jsonBytes, err := common.Marshal(requestBody) + if err != nil { + return nil, fmt.Errorf("marshal request body failed: %v", err) + } + + bodyStr := string(jsonBytes) + authHeader, err := wechatPayBuildAuthHeader("POST", path, bodyStr) + if err != nil { + return nil, err + } + + apiBaseURL := getWechatPayAPIBaseURL() + req, err := http.NewRequest("POST", apiBaseURL+path, strings.NewReader(bodyStr)) + if err != nil { + return nil, fmt.Errorf("create request failed: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", authHeader) + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "TokenFactory") + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %v", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response failed: %v", err) + } + + if resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 204 { + return nil, fmt.Errorf("WeChat Pay API error: HTTP %d, body: %s", resp.StatusCode, string(respBody)) + } + + var result map[string]interface{} + if len(respBody) > 0 { + if err := common.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("parse response failed: %v", err) + } + } + + return result, nil +} + +// wechatPayDecryptResource decrypts WeChat Pay notification resource +func wechatPayDecryptResource(ciphertext, associatedData, nonce, apiV3Key string) ([]byte, error) { + aesKey, err := base64.StdEncoding.DecodeString(apiV3Key) + if err != nil { + return nil, fmt.Errorf("decode api v3 key failed: %v", err) + } + + cipherData, err := base64.StdEncoding.DecodeString(ciphertext) + if err != nil { + return nil, fmt.Errorf("decode ciphertext failed: %v", err) + } + + nonceBytes := []byte(nonce) + adBytes := []byte(associatedData) + + block, err := aes.NewCipher(aesKey) + if err != nil { + return nil, fmt.Errorf("create cipher failed: %v", err) + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("create GCM failed: %v", err) + } + + plaintext, err := aesGCM.Open(nil, nonceBytes, cipherData, adBytes) + if err != nil { + return nil, fmt.Errorf("decrypt failed: %v", err) + } + + return plaintext, nil +} + +func (a *WechatPayAdaptor) RequestPay(c *gin.Context, req *WechatPayRequest) { + if req.PaymentMethod != PaymentMethodWechatPay { + c.JSON(200, gin.H{"message": "error", "data": "不支持的支付渠道"}) + return + } + + minTopup := getWechatPayMinTopUp() + if req.Amount < minTopup { + c.JSON(200, gin.H{"message": "error", "data": fmt.Sprintf("充值数量不能小于 %d", minTopup)}) + return + } + if req.Amount > 100000 { + c.JSON(200, gin.H{"message": "error", "data": "充值数量不能大于 100000"}) + return + } + + id := c.GetInt("id") + user, err := model.GetUserById(id, false) + if err != nil || user == nil { + c.JSON(200, gin.H{"message": "error", "data": "用户不存在"}) + return + } + + group, err := model.GetUserGroup(id, true) + if err != nil { + c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"}) + return + } + + payMoney := getWechatPayPayMoney(float64(req.Amount), group) + if payMoney <= 0.01 { + c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"}) + return + } + + tradeNo := fmt.Sprintf("WX%d%s%d", id, common.GetRandomString(12), time.Now().Unix()) + + topUp := &model.TopUp{ + UserId: id, + Amount: req.Amount, + Money: payMoney, + TradeNo: tradeNo, + PaymentMethod: PaymentMethodWechatPay, + CreateTime: time.Now().Unix(), + Status: common.TopUpStatusPending, + } + err = topUp.Insert() + if err != nil { + log.Printf("创建WeChat Pay订单失败: %v", err) + c.JSON(200, gin.H{"message": "error", "data": "创建订单失败"}) + return + } + + // Create WeChat Pay order via API v3 + callBackAddress := service.GetCallbackAddress() + notifyUrl := strings.TrimRight(callBackAddress, "/") + "/api/wechatpay/notify" + if setting.WechatPayNotifyUrl != "" { + notifyUrl = setting.WechatPayNotifyUrl + } + + amountFen := int64(payMoney * 100) // Convert yuan to fen + + log.Printf("WeChat Pay创建订单 - 用户: %d, 订单: %s, 金额: %.2f元 (%d分)", id, tradeNo, payMoney, amountFen) + + payParams := map[string]interface{}{ + "mchid": getWechatPayMchId(), + "out_trade_no": tradeNo, + "appid": getWechatPayAppId(), + "description": "TokenFactory TopUp", + "notify_url": notifyUrl, + "amount": map[string]interface{}{ + "total": amountFen, + "currency": "CNY", + }, + "payer": map[string]interface{}{ + "openid": "", // Will be obtained from WeChat OAuth + }, + "scene_info": map[string]interface{}{ + "payer_client_ip": c.ClientIP(), + }, + } + + // For H5 payment + payParams["scene_info"].(map[string]interface{})["h5_info"] = map[string]interface{}{ + "type": "Wap", + } + + result, err := wechatPayPost("/v3/pay/transactions/h5", payParams) + if err != nil { + log.Printf("WeChat Pay创建订单失败: %v", err) + c.JSON(200, gin.H{"message": "error", "data": "拉起支付失败"}) + return + } + + h5Url := "" + if result != nil { + if urlStr, ok := result["h5_url"].(string); ok { + h5Url = urlStr + } + } + + log.Printf("WeChat Pay订单创建成功 - 用户ID: %d, 订单号: %s, 金额: %.2f", id, tradeNo, payMoney) + + c.JSON(200, gin.H{ + "message": "success", + "data": gin.H{ + "pay_url": h5Url, + "trade_no": tradeNo, + }, + }) +} + +// WechatPayNotify handles the WeChat Pay payment notification +func WechatPayNotify(c *gin.Context) { + bodyBytes, err := io.ReadAll(c.Request.Body) + if err != nil { + log.Printf("读取WeChat Pay通知失败: %v", err) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "read body failed"}) + return + } + defer c.Request.Body.Close() + + log.Printf("收到WeChat Pay通知: %s", string(bodyBytes)) + + var notifyData struct { + Id string `json:"id"` + CreateTime string `json:"create_time"` + EventType string `json:"event_type"` + ResourceType string `json:"resource_type"` + Resource struct { + Algorithm string `json:"algorithm"` + Ciphertext string `json:"ciphertext"` + AssociatedData string `json:"associated_data"` + Nonce string `json:"nonce"` + OriginalType string `json:"original_type"` + } `json:"resource"` + Summary string `json:"summary"` + } + + if err := common.Unmarshal(bodyBytes, ¬ifyData); err != nil { + log.Printf("解析WeChat Pay通知失败: %v", err) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "parse failed"}) + return + } + + apiV3Key := getWechatPayApiV3Key() + if apiV3Key == "" { + log.Printf("WeChat Pay API v3 key not configured") + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "not configured"}) + return + } + + // Decrypt the resource + plaintext, err := wechatPayDecryptResource( + notifyData.Resource.Ciphertext, + notifyData.Resource.AssociatedData, + notifyData.Resource.Nonce, + apiV3Key, + ) + if err != nil { + log.Printf("WeChat Pay通知解密失败: %v", err) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "decrypt failed"}) + return + } + + log.Printf("WeChat Pay通知解密成功: %s", string(plaintext)) + + var paymentResult struct { + AppId string `json:"appid"` + MchId string `json:"mchid"` + OutTradeNo string `json:"out_trade_no"` + TransactionId string `json:"transaction_id"` + TradeType string `json:"trade_type"` + TradeState string `json:"trade_state"` + TradeStateDesc string `json:"trade_state_desc"` + BankType string `json:"bank_type"` + Attach string `json:"attach"` + SuccessTime string `json:"success_time"` + Payer struct { + OpenId string `json:"openid"` + } `json:"payer"` + Amount struct { + Total int `json:"total"` + PayerTotal int `json:"payer_total"` + Currency string `json:"currency"` + PayerCurrency string `json:"payer_currency"` + } `json:"amount"` + } + + if err := common.Unmarshal(plaintext, &paymentResult); err != nil { + log.Printf("解析WeChat Pay支付结果失败: %v", err) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "parse result failed"}) + return + } + + tradeNo := paymentResult.OutTradeNo + log.Printf("WeChat Pay支付结果: out_trade_no=%s, transaction_id=%s, trade_state=%s", + tradeNo, paymentResult.TransactionId, paymentResult.TradeState) + + if paymentResult.TradeState != "SUCCESS" { + log.Printf("WeChat Pay支付未成功: %s, trade_state=%s", tradeNo, paymentResult.TradeState) + c.JSON(http.StatusOK, gin.H{"code": "SUCCESS", "message": "ok"}) + return + } + + LockOrder(tradeNo) + defer UnlockOrder(tradeNo) + + topUp := model.GetTopUpByTradeNo(tradeNo) + if topUp == nil { + log.Printf("WeChat Pay通知未找到订单: %s", tradeNo) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "order not found"}) + return + } + + if topUp.Status != common.TopUpStatusPending { + log.Printf("WeChat Pay订单状态错误: %s, 当前状态: %s", tradeNo, topUp.Status) + c.JSON(http.StatusOK, gin.H{"code": "SUCCESS", "message": "ok"}) + return + } + + dAmount := decimal.NewFromInt(topUp.Amount) + dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit) + quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart()) + + if quotaToAdd <= 0 { + log.Printf("WeChat Pay无效的充值额度: %s", tradeNo) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "invalid quota"}) + return + } + + topUp.CompleteTime = common.GetTimestamp() + topUp.Status = common.TopUpStatusSuccess + if err := topUp.Update(); err != nil { + log.Printf("WeChat Pay更新订单失败: %v", err) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "update failed"}) + return + } + + if err := model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true); err != nil { + log.Printf("WeChat Pay更新用户额度失败: %v", err) + c.JSON(http.StatusOK, gin.H{"code": "FAIL", "message": "quota update failed"}) + return + } + + model.RecordLog(topUp.UserId, model.LogTypeTopup, + fmt.Sprintf("使用微信支付充值成功,充值金额: %v,支付金额:%.2f", + logger.LogQuota(quotaToAdd), topUp.Money)) + model.ApplyAffiliateTopupReward(topUp.UserId, quotaToAdd) + + log.Printf("WeChat Pay充值成功 - 订单: %s, 额度: %d", tradeNo, quotaToAdd) + + c.JSON(http.StatusOK, gin.H{"code": "SUCCESS", "message": "ok"}) +} + +// RequestWechatPay handles the WeChat Pay payment request from frontend +func RequestWechatPay(c *gin.Context) { + if !setting.WechatPayEnabled { + c.JSON(200, gin.H{"message": "error", "data": "微信支付未启用"}) + return + } + + var req WechatPayRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(200, gin.H{"message": "error", "data": "参数错误"}) + return + } + + wechatPayAdaptor.RequestPay(c, &req) +} diff --git a/model/option.go b/model/option.go index d60d0cb..f65a4ff 100644 --- a/model/option.go +++ b/model/option.go @@ -1,4 +1,4 @@ -package model +package model import ( "strconv" @@ -131,6 +131,31 @@ func InitOptionMap() { common.OptionMap["WaffoUnitPrice"] = strconv.FormatFloat(setting.WaffoUnitPrice, 'f', -1, 64) common.OptionMap["WaffoMinTopUp"] = strconv.Itoa(setting.WaffoMinTopUp) common.OptionMap["WaffoPayMethods"] = setting.WaffoPayMethods2JsonString() + common.OptionMap["AlipayEnabled"] = strconv.FormatBool(setting.AlipayEnabled) + common.OptionMap["AlipayAppId"] = setting.AlipayAppId + common.OptionMap["AlipayPrivateKey"] = setting.AlipayPrivateKey + common.OptionMap["AlipayPublicKey"] = setting.AlipayPublicKey + common.OptionMap["AlipayNotifyUrl"] = setting.AlipayNotifyUrl + common.OptionMap["AlipayReturnUrl"] = setting.AlipayReturnUrl + common.OptionMap["AlipayUnitPrice"] = strconv.FormatFloat(setting.AlipayUnitPrice, 'f', -1, 64) + common.OptionMap["AlipayMinTopUp"] = strconv.Itoa(setting.AlipayMinTopUp) + common.OptionMap["AlipaySandbox"] = strconv.FormatBool(setting.AlipaySandbox) + common.OptionMap["AlipaySandboxAppId"] = setting.AlipaySandboxAppId + common.OptionMap["AlipaySandboxPrivateKey"] = setting.AlipaySandboxPrivateKey + common.OptionMap["WechatPayEnabled"] = strconv.FormatBool(setting.WechatPayEnabled) + common.OptionMap["WechatPayAppId"] = setting.WechatPayAppId + common.OptionMap["WechatPayMchId"] = setting.WechatPayMchId + common.OptionMap["WechatPayApiV3Key"] = setting.WechatPayApiV3Key + common.OptionMap["WechatPayPrivateKey"] = setting.WechatPayPrivateKey + common.OptionMap["WechatPayCertSerial"] = setting.WechatPayCertSerial + common.OptionMap["WechatPayNotifyUrl"] = setting.WechatPayNotifyUrl + common.OptionMap["WechatPayReturnUrl"] = setting.WechatPayReturnUrl + common.OptionMap["WechatPayUnitPrice"] = strconv.FormatFloat(setting.WechatPayUnitPrice, 'f', -1, 64) + common.OptionMap["WechatPayMinTopUp"] = strconv.Itoa(setting.WechatPayMinTopUp) + common.OptionMap["WechatPaySandbox"] = strconv.FormatBool(setting.WechatPaySandbox) + common.OptionMap["WechatPaySandboxMchId"] = setting.WechatPaySandboxMchId + common.OptionMap["WechatPaySandboxApiV3Key"] = setting.WechatPaySandboxApiV3Key + common.OptionMap["WechatPaySandboxPrivateKey"] = setting.WechatPaySandboxPrivateKey common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString() common.OptionMap["Chats"] = setting.Chats2JsonString() common.OptionMap["AutoGroups"] = setting.AutoGroups2JsonString() @@ -501,6 +526,56 @@ func updateOptionMap(key string, value string) (err error) { case "WaffoApiKey": setting.WaffoApiKey = value case "WaffoPrivateKey": + case "AlipayEnabled": + setting.AlipayEnabled = value == "true" + case "AlipayAppId": + setting.AlipayAppId = value + case "AlipayPrivateKey": + setting.AlipayPrivateKey = value + case "AlipayPublicKey": + setting.AlipayPublicKey = value + case "AlipayNotifyUrl": + setting.AlipayNotifyUrl = value + case "AlipayReturnUrl": + setting.AlipayReturnUrl = value + case "AlipayUnitPrice": + setting.AlipayUnitPrice, _ = strconv.ParseFloat(value, 64) + case "AlipayMinTopUp": + setting.AlipayMinTopUp, _ = strconv.Atoi(value) + case "AlipaySandbox": + setting.AlipaySandbox = value == "true" + case "AlipaySandboxAppId": + setting.AlipaySandboxAppId = value + case "AlipaySandboxPrivateKey": + setting.AlipaySandboxPrivateKey = value + case "WechatPayEnabled": + setting.WechatPayEnabled = value == "true" + case "WechatPayAppId": + setting.WechatPayAppId = value + case "WechatPayMchId": + setting.WechatPayMchId = value + case "WechatPayApiV3Key": + setting.WechatPayApiV3Key = value + case "WechatPayPrivateKey": + setting.WechatPayPrivateKey = value + case "WechatPayCertSerial": + setting.WechatPayCertSerial = value + case "WechatPayNotifyUrl": + setting.WechatPayNotifyUrl = value + case "WechatPayReturnUrl": + setting.WechatPayReturnUrl = value + case "WechatPayUnitPrice": + setting.WechatPayUnitPrice, _ = strconv.ParseFloat(value, 64) + case "WechatPayMinTopUp": + setting.WechatPayMinTopUp, _ = strconv.Atoi(value) + case "WechatPaySandbox": + setting.WechatPaySandbox = value == "true" + case "WechatPaySandboxMchId": + setting.WechatPaySandboxMchId = value + case "WechatPaySandboxApiV3Key": + setting.WechatPaySandboxApiV3Key = value + case "WechatPaySandboxPrivateKey": + setting.WechatPaySandboxPrivateKey = value setting.WaffoPrivateKey = value case "WaffoPublicCert": setting.WaffoPublicCert = value diff --git a/router/api-router.go b/router/api-router.go index 021d351..837bcdd 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -1,4 +1,4 @@ -package router +package router import ( "github.com/QuantumNous/new-api/controller" @@ -104,6 +104,10 @@ func SetApiRouter(router *gin.Engine) { apiRouter.POST("/creem/webhook", controller.CreemWebhook) apiRouter.POST("/waffo/webhook", controller.WaffoWebhook) + apiRouter.POST("/alipay/notify", controller.AlipayNotify) + apiRouter.GET("/alipay/return", controller.AlipayReturn) + apiRouter.POST("/alipay/return", controller.AlipayReturn) + apiRouter.POST("/wechatpay/notify", controller.WechatPayNotify) // Universal secure verification routes apiRouter.POST("/verify", middleware.UserAuth(), middleware.CriticalRateLimit(), controller.UniversalVerify) @@ -165,6 +169,8 @@ func SetApiRouter(router *gin.Engine) { selfRoute.POST("/waffo/pay", middleware.CriticalRateLimit(), controller.RequestWaffoPay) selfRoute.POST("/aff_transfer", controller.TransferAffQuota) selfRoute.GET("/aff_invitees", controller.GetAffInvitees) + selfRoute.POST("/alipay/pay", middleware.CriticalRateLimit(), controller.RequestAlipayPay) + selfRoute.POST("/wechatpay/pay", middleware.CriticalRateLimit(), controller.RequestWechatPay) selfRoute.PUT("/setting", controller.UpdateUserSetting) selfRoute.POST("/supplier/application", controller.SubmitSupplierApplication) selfRoute.GET("/supplier/application/self", controller.GetMySupplierApplication) diff --git a/setting/payment_alipay.go b/setting/payment_alipay.go new file mode 100644 index 0000000..08a2868 --- /dev/null +++ b/setting/payment_alipay.go @@ -0,0 +1,16 @@ +package setting + +// Alipay direct integration (via Alipay+ / international Alipay API) +var ( + AlipayEnabled bool + AlipayAppId string + AlipayPrivateKey string + AlipayPublicKey string + AlipayNotifyUrl string + AlipayReturnUrl string + AlipayUnitPrice float64 = 1.0 + AlipayMinTopUp int = 1 + AlipaySandbox bool + AlipaySandboxAppId string + AlipaySandboxPrivateKey string +) diff --git a/setting/payment_wechat.go b/setting/payment_wechat.go new file mode 100644 index 0000000..af6bb44 --- /dev/null +++ b/setting/payment_wechat.go @@ -0,0 +1,19 @@ +package setting + +// WeChat Pay direct integration (via WeChat Pay APIv3) +var ( + WechatPayEnabled bool + WechatPayAppId string + WechatPayMchId string + WechatPayApiV3Key string + WechatPayPrivateKey string + WechatPayCertSerial string + WechatPayNotifyUrl string + WechatPayReturnUrl string + WechatPayUnitPrice float64 = 1.0 + WechatPayMinTopUp int = 1 + WechatPaySandbox bool + WechatPaySandboxMchId string + WechatPaySandboxApiV3Key string + WechatPaySandboxPrivateKey string +) diff --git a/web/src/components/settings/PaymentSetting.jsx b/web/src/components/settings/PaymentSetting.jsx index 1716527..92a11d3 100644 --- a/web/src/components/settings/PaymentSetting.jsx +++ b/web/src/components/settings/PaymentSetting.jsx @@ -1,4 +1,4 @@ -/* +/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify @@ -17,104 +17,132 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useEffect, useState } from 'react'; -import { Card, Spin } from '@douyinfe/semi-ui'; -import SettingsGeneralPayment from '../../pages/Setting/Payment/SettingsGeneralPayment'; -import SettingsPaymentGateway from '../../pages/Setting/Payment/SettingsPaymentGateway'; -import SettingsPaymentGatewayStripe from '../../pages/Setting/Payment/SettingsPaymentGatewayStripe'; -import SettingsPaymentGatewayCreem from '../../pages/Setting/Payment/SettingsPaymentGatewayCreem'; -import SettingsPaymentGatewayWaffo from '../../pages/Setting/Payment/SettingsPaymentGatewayWaffo'; -import { API, showError, toBoolean } from '../../helpers'; -import { useTranslation } from 'react-i18next'; +import React, { useEffect, useState } from "react"; +import { Card, Spin } from "@douyinfe/semi-ui"; +import SettingsGeneralPayment from "../../pages/Setting/Payment/SettingsGeneralPayment"; +import SettingsPaymentGateway from "../../pages/Setting/Payment/SettingsPaymentGateway"; +import SettingsPaymentGatewayStripe from "../../pages/Setting/Payment/SettingsPaymentGatewayStripe"; +import SettingsPaymentGatewayCreem from "../../pages/Setting/Payment/SettingsPaymentGatewayCreem"; +import SettingsPaymentGatewayWaffo from "../../pages/Setting/Payment/SettingsPaymentGatewayWaffo"; +import SettingsPaymentGatewayAlipay from "../../pages/Setting/Payment/SettingsPaymentGatewayAlipay"; +import SettingsPaymentGatewayWechat from "../../pages/Setting/Payment/SettingsPaymentGatewayWechat"; +import { API, showError, toBoolean } from "../../helpers"; +import { useTranslation } from "react-i18next"; /** 系统设置中的支付 Tab:拉取 option 并渲染各支付子表单。 */ const PaymentSetting = () => { const { t } = useTranslation(); let [inputs, setInputs] = useState({ - ServerAddress: '', - PayAddress: '', - OnlinePayProvider: 'yipay', - EpayId: '', - EpayKey: '', - YipayAppSecret: '', - YipayMchNo: '', - YipayAppId: '', - YipayNotifyUrl: '', - YipayReturnUrl: '', - YipayChannelExtra: '', - YipayRequestURL: '', + ServerAddress: "", + PayAddress: "", + OnlinePayProvider: "yipay", + EpayId: "", + EpayKey: "", + YipayAppSecret: "", + YipayMchNo: "", + YipayAppId: "", + YipayNotifyUrl: "", + YipayReturnUrl: "", + YipayChannelExtra: "", + YipayRequestURL: "", Price: 7.3, MinTopUp: 1, - TopupGroupRatio: '', - CustomCallbackAddress: '', - PayMethods: '', - AmountOptions: '', - AmountDiscount: '', + TopupGroupRatio: "", + CustomCallbackAddress: "", + PayMethods: "", + AmountOptions: "", + AmountDiscount: "", - StripeApiSecret: '', - StripeWebhookSecret: '', - StripePriceId: '', + StripeApiSecret: "", + StripeWebhookSecret: "", + StripePriceId: "", StripeUnitPrice: 8.0, StripeMinTopUp: 1, StripePromotionCodesEnabled: false, + + AlipayEnabled: false, + AlipayAppId: "", + AlipayPrivateKey: "", + AlipayPublicKey: "", + AlipayNotifyUrl: "", + AlipayReturnUrl: "", + AlipayUnitPrice: 1.0, + AlipayMinTopUp: 1, + AlipaySandbox: false, + AlipaySandboxAppId: "", + AlipaySandboxPrivateKey: "", + WechatPayEnabled: false, + WechatPayAppId: "", + WechatPayMchId: "", + WechatPayApiV3Key: "", + WechatPayPrivateKey: "", + WechatPayCertSerial: "", + WechatPayNotifyUrl: "", + WechatPayReturnUrl: "", + WechatPayUnitPrice: 1.0, + WechatPayMinTopUp: 1, + WechatPaySandbox: false, + WechatPaySandboxMchId: "", + WechatPaySandboxApiV3Key: "", + WechatPaySandboxPrivateKey: "", }); let [loading, setLoading] = useState(false); const getOptions = async () => { - const res = await API.get('/api/option/'); + const res = await API.get("/api/option/"); const { success, message, data } = res.data; if (success) { let newInputs = {}; data.forEach((item) => { switch (item.key) { - case 'TopupGroupRatio': + case "TopupGroupRatio": try { newInputs[item.key] = JSON.stringify( JSON.parse(item.value), null, - 2, + 2 ); } catch (error) { newInputs[item.key] = item.value; } break; - case 'payment_setting.amount_options': + case "payment_setting.amount_options": try { - newInputs['AmountOptions'] = JSON.stringify( + newInputs["AmountOptions"] = JSON.stringify( JSON.parse(item.value), null, - 2, + 2 ); } catch (error) { - newInputs['AmountOptions'] = item.value; + newInputs["AmountOptions"] = item.value; } break; - case 'payment_setting.amount_discount': + case "payment_setting.amount_discount": try { - newInputs['AmountDiscount'] = JSON.stringify( + newInputs["AmountDiscount"] = JSON.stringify( JSON.parse(item.value), null, - 2, + 2 ); } catch (error) { - newInputs['AmountDiscount'] = item.value; + newInputs["AmountDiscount"] = item.value; } break; - case 'Price': - case 'MinTopUp': - case 'StripeUnitPrice': - case 'StripeMinTopUp': + case "Price": + case "MinTopUp": + case "StripeUnitPrice": + case "StripeMinTopUp": newInputs[item.key] = parseFloat(item.value); break; - case 'YipayAppSecret': + case "YipayAppSecret": newInputs[item.key] = item.value != null && item.value !== undefined ? String(item.value) - : ''; + : ""; break; default: - if (item.key.endsWith('Enabled')) { + if (item.key.endsWith("Enabled")) { newInputs[item.key] = toBoolean(item.value); } else { newInputs[item.key] = item.value; @@ -134,7 +162,7 @@ const PaymentSetting = () => { setLoading(true); await getOptions(); } catch (error) { - showError(t('刷新失败')); + showError(t("刷新失败")); } finally { setLoading(false); } @@ -146,22 +174,28 @@ const PaymentSetting = () => { return ( <> - - + + - + - + - + - + + + + + + + ); diff --git a/web/src/components/topup/index.jsx b/web/src/components/topup/index.jsx index ac8072e..d37e537 100644 --- a/web/src/components/topup/index.jsx +++ b/web/src/components/topup/index.jsx @@ -1,4 +1,4 @@ - + import React, { useEffect, useState, useContext } from 'react'; import { useSearchParams } from 'react-router-dom'; import { @@ -58,6 +58,8 @@ const TopUp = () => { // Waffo 相关状态 const [enableWaffoTopUp, setEnableWaffoTopUp] = useState(false); + const [enableAlipayTopUp, setEnableAlipayTopUp] = useState(false); + const [enableWechatPayTopUp, setEnableWechatPayTopUp] = useState(false); const [waffoPayMethods, setWaffoPayMethods] = useState([]); const [waffoMinTopUp, setWaffoMinTopUp] = useState(1); @@ -147,6 +149,10 @@ const TopUp = () => { showError(t('管理员未开启Stripe充值!')); return; } + } else if (payment === 'alipay') { + // Alipay handles its own check via backend + } else if (payment === 'wxpay') { + // WeChat Pay handles its own check via backend } else { if (!enableOnlineTopUp) { showError(t('管理员未开启在线充值!')); @@ -159,6 +165,10 @@ const TopUp = () => { try { if (payment === 'stripe') { await getStripeAmount(); + } else if (payment === 'alipay') { + // Alipay goes directly to payment without amount query + } else if (payment === 'wxpay') { + // WeChat Pay goes directly to payment without amount query } else { await getAmount(); } @@ -201,7 +211,19 @@ const TopUp = () => { amount: parseInt(topUpCount), payment_method: 'stripe', }); - } else { + } else if (payWay === 'alipay') { + // Alipay 支付请求 + res = await API.post('/api/user/alipay/pay', { + amount: parseInt(topUpCount), + payment_method: 'alipay', + }); + } else if (payWay === 'wxpay') { + // WeChat Pay 支付请求 + res = await API.post('/api/user/wechatpay/pay', { + amount: parseInt(topUpCount), + payment_method: 'wxpay', + }); + } else { // 普通支付请求 res = await API.post('/api/user/pay', { amount: parseInt(topUpCount), @@ -215,7 +237,10 @@ const TopUp = () => { if (payWay === 'stripe') { // Stripe 支付回调处理 window.open(data.pay_link, '_blank'); - } else { + } else if (payWay === 'alipay' || payWay === 'wxpay') { + // Alipay / WeChat Pay 返回 pay_url,直接打开新窗口跳转 + window.open(data.pay_url, '_blank'); + } else { // 普通支付表单提交 let params = data; let url = res.data.url; @@ -513,6 +538,10 @@ const TopUp = () => { setEnableStripeTopUp(enableStripeTopUp); setEnableCreemTopUp(enableCreemTopUp); const enableWaffoTopUp = data.enable_waffo_topup || false; + const enableAlipayTopUp = data.enable_alipay_topup || false; + setEnableAlipayTopUp(enableAlipayTopUp); + const enableWechatPayTopUp = data.enable_wechatpay_topup || false; + setEnableWechatPayTopUp(enableWechatPayTopUp); setEnableWaffoTopUp(enableWaffoTopUp); setWaffoPayMethods(data.waffo_pay_methods || []); setWaffoMinTopUp(data.waffo_min_topup || 1); @@ -607,7 +636,10 @@ const TopUp = () => { statusState.status.recharge_display_currency || 'USD', ); - + const enableAlipayTopUp = statusState.status.enable_alipay_topup || false; + setEnableAlipayTopUp(enableAlipayTopUp); + const enableWechatPayTopUp = statusState.status.enable_wechatpay_topup || false; + setEnableWechatPayTopUp(enableWechatPayTopUp); setStatusLoading(false); } }, [statusState?.status]); @@ -1005,3 +1037,9 @@ const TopUp = () => { }; export default TopUp; + + + + + + diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 16f27b6..f607464 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -5587,6 +5587,92 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "Default ap-guangzhou; can be changed to ap-beijing, etc.", "(共 {{total}} 个)": "({{total}} total)", "(共 {{total}} 个,省略 {{omit}} 个)": "({{total}} total, {{omit}} omitted)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Currently supports Yipay configuration; by default, the server address above is used as the callback URL!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Currently supports Yipay configuration; by default, the server address above is used as the callback URL!)", + "热度配置": "Heat Configuration", + "模型热度排行配置": "Model Heat Ranking Config", + "热度分 = (手动基数 + 实际调用) x 权重": "Heat Score = (Manual Base + Actual Calls) x Weight", + "预估热度分": "Estimated Heat Score", + "统计周期": "Statistics Period", + "统计周期设置": "Statistics Period Setting", + "统计周期已更新,重新加载数据中...": "Statistics period updated, reloading data...", + "权重配置": "Weight Configuration", + "手动调整": "Manual Adjustment", + "实际调用": "Actual Calls", + "全部历史": "All History", + "近 7 天": "Last 7 Days", + "近 30 天": "Last 30 Days", + "模型 / 渠道": "Model / Channel", + "搜索渠道": "Search Channel", + "不修改": "No Change", + "批量保存成功": "Batch saved successfully", + "批量保存失败": "Batch save failed", + "序号": "No.", + "热门": "Hot", + "操作记录": "Operation Records", + "系统操作日志审计": "System Operation Log Audit", + "操作详情": "Operation Details", + "操作描述": "Operation Description", + "操作者": "Operator", + "资源ID": "Resource ID", + "资源类型": "Resource Type", + "资源名称": "Resource Name", + "IP地址": "IP Address", + "兑换码": "Redemption Code", + "角色菜单权限配置": "Role Menu Permission Config", + "重置当前角色": "Reset Current Role", + "已重置角色配置": "Role configuration has been reset", + "供应商申请": "Supplier Application", + "供应商-申请": "Supplier - Application", + "供应商-渠道管理": "Supplier - Channel Management", + "供应商渠道管理": "Supplier Channel Management", + "供应商-定价设置": "Supplier - Pricing Settings", + "供应商定价设置": "Supplier Pricing Settings", + "供应商-数据看板": "Supplier - Dashboard", + "供应商数据看板": "Supplier Dashboard", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 23e0b78..34a9927 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "La valeur par défaut est ap-guangzhou, qui peut être modifiée en ap-beijing etc.", "(共 {{total}} 个)": "(Total {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(Total des éléments {{total}}, éléments {{omit}} omis)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Prend actuellement en charge la configuration Yipay/Yipay et utilise l'adresse du serveur ci-dessus comme adresse de rappel par défaut !)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Prend actuellement en charge la configuration Yipay/Yipay et utilise l'adresse du serveur ci-dessus comme adresse de rappel par défaut !)", + "热度配置": "Configuration de chaleur", + "模型热度排行配置": "Configuration du classement de chaleur des modeles", + "热度分 = (手动基数 + 实际调用) x 权重": "Score de chaleur = (Base manuelle + Appels reels) x Poids", + "预估热度分": "Score de chaleur estime", + "统计周期": "Periode statistique", + "统计周期设置": "Parametre de periode statistique", + "统计周期已更新,重新加载数据中...": "Periode mise a jour, rechargement des donnees...", + "权重配置": "Configuration des poids", + "手动调整": "Ajustement manuel", + "实际调用": "Appels reels", + "全部历史": "Tout historique", + "近 7 天": "7 derniers jours", + "近 30 天": "30 derniers jours", + "模型 / 渠道": "Modele / Canal", + "搜索渠道": "Rechercher canal", + "不修改": "Ne pas modifier", + "批量保存成功": "Enregistrement par lot reussi", + "批量保存失败": "Echec enregistrement par lot", + "序号": "No.", + "热门": "Populaire", + "操作记录": "Registre des operations", + "系统操作日志审计": "Audit des journaux d'operations systeme", + "操作详情": "Details de l'operation", + "操作描述": "Description de l'operation", + "操作者": "Operateur", + "资源ID": "ID de ressource", + "资源类型": "Type de ressource", + "资源名称": "Nom de la ressource", + "IP地址": "Adresse IP", + "兑换码": "Code d'echange", + "角色菜单权限配置": "Configuration des permissions du menu de role", + "重置当前角色": "Reinitialiser le role actuel", + "已重置角色配置": "La configuration du role a ete reinitialisee", + "成本价": "Prix de revient", + "供应商申请": "Demande de fournisseur", + "供应商-申请": "Fournisseur - Demande", + "供应商-渠道管理": "Fournisseur - Gestion des canaux", + "供应商渠道管理": "Gestion des canaux du fournisseur", + "供应商-定价设置": "Fournisseur - Parametres de prix", + "供应商定价设置": "Parametres de prix du fournisseur", + "供应商-数据看板": "Fournisseur - Tableau de bord", + "供应商数据看板": "Tableau de bord du fournisseur", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/id.json b/web/src/i18n/locales/id.json index 6e5e0f3..cfee086 100644 --- a/web/src/i18n/locales/id.json +++ b/web/src/i18n/locales/id.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "Standarnya adalah ap-guangzhou, yang dapat diubah menjadi ap-beijing dll.", "(共 {{total}} 个)": "(Jumlah {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(Total item {{total}}, item {{omit}} dihilangkan)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Saat ini mendukung konfigurasi Yipay/Yipay, dan menggunakan alamat server di atas sebagai alamat panggilan balik secara default!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Saat ini mendukung konfigurasi Yipay/Yipay, dan menggunakan alamat server di atas sebagai alamat panggilan balik secara default!)", + "热度配置": "Konfigurasi Panas", + "模型热度排行配置": "Konfigurasi Peringkat Panas Model", + "热度分 = (手动基数 + 实际调用) x 权重": "Skor Panas = (Dasar Manual + Panggilan Aktual) x Bobot", + "预估热度分": "Perkiraan Skor Panas", + "统计周期": "Periode Statistik", + "统计周期设置": "Pengaturan Periode Statistik", + "统计周期已更新,重新加载数据中...": "Periode diperbarui, memuat ulang data...", + "权重配置": "Konfigurasi Bobot", + "手动调整": "Penyesuaian Manual", + "实际调用": "Panggilan Aktual", + "全部历史": "Semua Riwayat", + "近 7 天": "7 Hari Terakhir", + "近 30 天": "30 Hari Terakhir", + "模型 / 渠道": "Model / Saluran", + "搜索渠道": "Cari Saluran", + "不修改": "Tidak Berubah", + "批量保存成功": "Penyimpanan massal berhasil", + "批量保存失败": "Penyimpanan massal gagal", + "序号": "No.", + "热门": "Populer", + "操作记录": "Catatan Operasi", + "系统操作日志审计": "Audit Log Operasi Sistem", + "操作详情": "Detail Operasi", + "操作描述": "Deskripsi Operasi", + "操作者": "Operator", + "资源ID": "ID Sumber Daya", + "资源类型": "Tipe Sumber Daya", + "资源名称": "Nama Sumber Daya", + "IP地址": "Alamat IP", + "兑换码": "Kode Penukaran", + "角色菜单权限配置": "Konfigurasi Izin Menu Peran", + "重置当前角色": "Reset Peran Saat Ini", + "已重置角色配置": "Konfigurasi peran telah direset", + "成本价": "Harga Pokok", + "供应商申请": "Aplikasi Pemasok", + "供应商-申请": "Pemasok - Aplikasi", + "供应商-渠道管理": "Pemasok - Manajemen Saluran", + "供应商渠道管理": "Manajemen Saluran Pemasok", + "供应商-定价设置": "Pemasok - Pengaturan Harga", + "供应商定价设置": "Pengaturan Harga Pemasok", + "供应商-数据看板": "Pemasok - Dashboard", + "供应商数据看板": "Dashboard Pemasok", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index 0cccbb1..0b2c823 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "デフォルトは ap-guangzhou ですが、ap-beijing などに変更できます。", "(共 {{total}} 个)": "(合計 {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(合計{{total}}項目、{{omit}}項目は省略)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(現在 Yipay/Yipay 構成をサポートしており、デフォルトで上記のサーバー アドレスをコールバック アドレスとして使用します。)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(現在 Yipay/Yipay 構成をサポートしており、デフォルトで上記のサーバー アドレスをコールバック アドレスとして使用します。)", + "热度配置": "ヒート設定", + "模型热度排行配置": "モデルヒートランキング設定", + "热度分 = (手动基数 + 实际调用) x 权重": "ヒートスコア = (手動ベース + 實際呼出) x 重み", + "预估热度分": "推定ヒートスコア", + "统计周期": "統計期間", + "统计周期设置": "統計期間設定", + "统计周期已更新,重新加载数据中...": "統計期間が更新されました。データを再読込中...", + "权重配置": "重み設定", + "手动调整": "手動調整", + "实际调用": "實際呼出", + "全部历史": "全履歴", + "近 7 天": "最近7日間", + "近 30 天": "最近30日間", + "模型 / 渠道": "モデル / チャンネル", + "搜索渠道": "チャンネル検索", + "不修改": "変更しない", + "批量保存成功": "一括保存成功", + "批量保存失败": "一括保存失敗", + "序号": "番号", + "热门": "人気", + "操作记录": "操作記録", + "系统操作日志审计": "システム操作ログ監査", + "操作详情": "操作詳細", + "操作描述": "操作説明", + "操作者": "操作者", + "资源ID": "リソースID", + "资源类型": "リソースタイプ", + "资源名称": "リソース名", + "IP地址": "IPアドレス", + "兑换码": "交換コード", + "角色菜单权限配置": "ロールメニュー権限設定", + "重置当前角色": "現在のロールをリセット", + "已重置角色配置": "ロール設定をリセットしました", + "成本价": "原価", + "供应商申请": "サプライヤー申請", + "供应商-申请": "サプライヤー - 申請", + "供应商-渠道管理": "サプライヤー - チャンネル管理", + "供应商渠道管理": "サプライヤーチャンネル管理", + "供应商-定价设置": "サプライヤー - 価格設定", + "供应商定价设置": "サプライヤー価格設定", + "供应商-数据看板": "サプライヤー - ダッシュボード", + "供应商数据看板": "サプライヤーダッシュボード", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/ms.json b/web/src/i18n/locales/ms.json index 41e19b7..bf866d8 100644 --- a/web/src/i18n/locales/ms.json +++ b/web/src/i18n/locales/ms.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "Lalai ialah ap-guangzhou, yang boleh ditukar kepada ap-beijing dsb.", "(共 {{total}} 个)": "(Jumlah {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(Jumlah {{total}} item, ditinggalkan {{omit}} item)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Pada masa ini menyokong konfigurasi Yipay/Yipay, dan menggunakan alamat pelayan di atas sebagai alamat panggilan balik secara lalai!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Pada masa ini menyokong konfigurasi Yipay/Yipay, dan menggunakan alamat pelayan di atas sebagai alamat panggilan balik secara lalai!)", + "热度配置": "Konfigurasi Haba", + "模型热度排行配置": "Konfigurasi Kedudukan Haba Model", + "热度分 = (手动基数 + 实际调用) x 权重": "Skor Haba = (Asas Manual + Panggilan Sebenar) x Berat", + "预估热度分": "Anggaran Skor Haba", + "统计周期": "Tempoh Statistik", + "统计周期设置": "Tetapan Tempoh Statistik", + "统计周期已更新,重新加载数据中...": "Tempoh dikemas kini, memuat semula data...", + "权重配置": "Konfigurasi Berat", + "手动调整": "Pelarasan Manual", + "实际调用": "Panggilan Sebenar", + "全部历史": "Semua Sejarah", + "近 7 天": "7 Hari Terakhir", + "近 30 天": "30 Hari Terakhir", + "模型 / 渠道": "Model / Saluran", + "搜索渠道": "Cari Saluran", + "不修改": "Tiada Perubahan", + "批量保存成功": "Simpan pukal berjaya", + "批量保存失败": "Simpan pukal gagal", + "序号": "No.", + "热门": "Popular", + "操作记录": "Rekod Operasi", + "系统操作日志审计": "Audit Log Operasi Sistem", + "操作详情": "Butiran Operasi", + "操作描述": "Penerangan Operasi", + "操作者": "Pengendali", + "资源ID": "ID Sumber", + "资源类型": "Jenis Sumber", + "资源名称": "Nama Sumber", + "IP地址": "Alamat IP", + "兑换码": "Kod Tebusan", + "角色菜单权限配置": "Konfigurasi Kebenaran Menu Peranan", + "重置当前角色": "Tetapkan Semula Peranan Semasa", + "已重置角色配置": "Konfigurasi peranan telah ditetapkan semula", + "成本价": "Kos", + "供应商申请": "Permohonan Pembekal", + "供应商-申请": "Pembekal - Permohonan", + "供应商-渠道管理": "Pembekal - Pengurusan Saluran", + "供应商渠道管理": "Pengurusan Saluran Pembekal", + "供应商-定价设置": "Pembekal - Tetapan Harga", + "供应商定价设置": "Tetapan Harga Pembekal", + "供应商-数据看板": "Pembekal - Papan Pemuka", + "供应商数据看板": "Papan Pemuka Pembekal", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 3f98963..ecbf543 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "По умолчанию используется ap-guangzhou, который можно изменить на ap-beijing и т. д.", "(共 {{total}} 个)": "(Всего {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(Всего элементов {{total}}, пропущены элементы {{omit}})", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(В настоящее время поддерживается конфигурация Yipay/Yipay и по умолчанию используется указанный выше адрес сервера в качестве адреса обратного вызова!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(В настоящее время поддерживается конфигурация Yipay/Yipay и по умолчанию используется указанный выше адрес сервера в качестве адреса обратного вызова!)", + "热度配置": "Конфигурация тепла", + "模型热度排行配置": "Конфигурация рейтинга тепла моделей", + "热度分 = (手动基数 + 实际调用) x 权重": "Оценка тепла = (Ручная база + Факт. вызовы) x Вес", + "预估热度分": "Предполагаемая оценка тепла", + "统计周期": "Период статистики", + "统计周期设置": "Настройка периода статистики", + "统计周期已更新,重新加载数据中...": "Период обновлён, перезагрузка данных...", + "权重配置": "Настройка веса", + "手动调整": "Ручная настройка", + "实际调用": "Фактические вызовы", + "全部历史": "Вся история", + "近 7 天": "Последние 7 дней", + "近 30 天": "Последние 30 дней", + "模型 / 渠道": "Модель / Канал", + "搜索渠道": "Поиск канала", + "不修改": "Не изменять", + "批量保存成功": "Массовое сохранение успешно", + "批量保存失败": "Ошибка массового сохранения", + "序号": "No.", + "热门": "Популярное", + "操作记录": "Записи операций", + "系统操作日志审计": "Аудит журнала операций системы", + "操作详情": "Детали операции", + "操作描述": "Описание операции", + "操作者": "Оператор", + "资源ID": "ID ресурса", + "资源类型": "Тип ресурса", + "资源名称": "Имя ресурса", + "IP地址": "IP-адрес", + "兑换码": "Код обмена", + "角色菜单权限配置": "�������������� ������ ������ ������", + "重置当前角色": "Сбросить текущую роль", + "已重置角色配置": "Конфигурация роли была сброшена", + "成本价": "Себестоимость", + "供应商申请": "Заявка поставщика", + "供应商-申请": "Поставщик - Заявка", + "供应商-渠道管理": "Поставщик - Управление каналами", + "供应商渠道管理": "Управление каналами поставщика", + "供应商-定价设置": "Поставщик - Настройка цен", + "供应商定价设置": "Настройка цен поставщика", + "供应商-数据看板": "Поставщик - Панель управления", + "供应商数据看板": "Панель управления поставщика", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/sw.json b/web/src/i18n/locales/sw.json index b248c28..97cbccf 100644 --- a/web/src/i18n/locales/sw.json +++ b/web/src/i18n/locales/sw.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "Chaguo-msingi ni ap-guangzhou, ambayo inaweza kubadilishwa kuwa ap-beijing n.k.", "(共 {{total}} 个)": "(Jumla {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(Jumla ya vipengee {{total}}, vilivyoachwa {{omit}})", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Kwa sasa inaauni usanidi wa Yipay/Yipay, na hutumia anwani ya seva iliyo hapo juu kama anwani ya kupiga simu kwa chaguomsingi!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Kwa sasa inaauni usanidi wa Yipay/Yipay, na hutumia anwani ya seva iliyo hapo juu kama anwani ya kupiga simu kwa chaguomsingi!)", + "热度配置": "Usanidi wa Joto", + "模型热度排行配置": "Usanidi wa Kiwango cha Joto cha Mfano", + "热度分 = (手动基数 + 实际调用) x 权重": "Alama ya Joto = (Msingi wa Mikono + Simu Halisi) x Uzito", + "预估热度分": "Alama ya Joto Inayokadiriwa", + "统计周期": "Kipindi cha Takwimu", + "统计周期设置": "Mpangilio wa Kipindi cha Takwimu", + "统计周期已更新,重新加载数据中...": "Kipindi kimesasishwa, inapakia upya data...", + "权重配置": "Usanidi wa Uzito", + "手动调整": "Marekebisho ya Mikono", + "实际调用": "Simu Halisi", + "全部历史": "Historia Yote", + "近 7 天": "Siku 7 Zilizopita", + "近 30 天": "Siku 30 Zilizopita", + "模型 / 渠道": "Mfano / Chaneli", + "搜索渠道": "Tafuta Chaneli", + "不修改": "Usibadilishe", + "批量保存成功": "Uhifadhi wa kundi umefaulu", + "批量保存失败": "Uhifadhi wa kundi umeshindwa", + "序号": "Nambari", + "热门": "Maarufu", + "操作记录": "Rekodi za Uendeshaji", + "系统操作日志审计": "Ukaguzi wa Kumbukumbu za Uendeshaji wa Mfumo", + "操作详情": "Maelezo ya Uendeshaji", + "操作描述": "Maelezo ya Uendeshaji", + "操作者": "Mwendeshaji", + "资源ID": "Kitambulisho cha Rasilimali", + "资源类型": "Aina ya Rasilimali", + "资源名称": "Jina la Rasilimali", + "IP地址": "Anwani ya IP", + "兑换码": "Msimbo wa Ukombozi", + "角色菜单权限配置": "Usanidi wa Ruhusa za Menyu ya Wajibu", + "重置当前角色": "Weka upya Wajibu wa Sasa", + "已重置角色配置": "Usanidi wa wajibu umewekwa upya", + "成本价": "Bei ya Gharama", + "供应商申请": "Maombi ya Msambazaji", + "供应商-申请": "Msambazaji - Maombi", + "供应商-渠道管理": "Msambazaji - Usimamizi wa Chaneli", + "供应商渠道管理": "Usimamizi wa Chaneli za Msambazaji", + "供应商-定价设置": "Msambazaji - Mipangilio ya Bei", + "供应商定价设置": "Mipangilio ya Bei ya Msambazaji", + "供应商-数据看板": "Msambazaji - Dashibodi", + "供应商数据看板": "Dashibodi ya Msambazaji", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/th.json b/web/src/i18n/locales/th.json index a2e9ac1..5da7d98 100644 --- a/web/src/i18n/locales/th.json +++ b/web/src/i18n/locales/th.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "ค่าเริ่มต้นคือ ap-guangzhou ซึ่งสามารถเปลี่ยนเป็น ap-beijing เป็นต้น", "(共 {{total}} 个)": "(รวม {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(ทั้งหมด {{total}} รายการ ละเว้น {{omit}} รายการ)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(ปัจจุบันรองรับการกำหนดค่า Yipay/Yipay และใช้ที่อยู่เซิร์ฟเวอร์ด้านบนเป็นที่อยู่โทรกลับตามค่าเริ่มต้น!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(ปัจจุบันรองรับการกำหนดค่า Yipay/Yipay และใช้ที่อยู่เซิร์ฟเวอร์ด้านบนเป็นที่อยู่โทรกลับตามค่าเริ่มต้น!)", + "热度配置": "การตั้งค่าความร้อน", + "模型热度排行配置": "การตั้งค่าการจัดอันดับความร้อนของโมเดล", + "热度分 = (手动基数 + 实际调用) x 权重": "คะแนนความร้อน = (ฐาน manual + การเรียกจริง) x น้ำหนัก", + "预估热度分": "คะแนนความร้อนโดยประมาณ", + "统计周期": "รอบระยะเวลาสถิติ", + "统计周期设置": "การตั้งค่ารอบระยะเวลาสถิติ", + "统计周期已更新,重新加载数据中...": "อัปเดตรอบระยะเวลาแล้ว กำลังโหลดข้อมูลใหม่...", + "权重配置": "การตั้งค่าน้ำหนัก", + "手动调整": "การปรับด้วยตนเอง", + "实际调用": "การเรียกจริง", + "全部历史": "ประวัติทั้งหมด", + "近 7 天": "7 วันที่ผ่านมา", + "近 30 天": "30 วันที่ผ่านมา", + "模型 / 渠道": "โมเดล / ช่องทาง", + "搜索渠道": "ค้นหาช่องทาง", + "不修改": "ไม่เปลี่ยนแปลง", + "批量保存成功": "บันทึกเป็นกลุ่มสำเร็จ", + "批量保存失败": "บันทึกเป็นกลุ่มล้มเหลว", + "序号": "ลำดับ", + "热门": "ยอดนิยม", + "操作记录": "บันทึกการดำเนินการ", + "系统操作日志审计": "การตรวจสอบบันทึกการดำเนินงานของระบบ", + "操作详情": "รายละเอียดการดำเนินการ", + "操作描述": "คำอธิบายการดำเนินการ", + "操作者": "ผู้ดำเนินการ", + "资源ID": "ID ทรัพยากร", + "资源类型": "ประเภททรัพยากร", + "资源名称": "ชื่อทรัพยากร", + "IP地址": "ที่อยู่ IP", + "兑换码": "รหัสแลก", + "角色菜单权限配置": "การกำหนดค่าสิทธิ์เมนูบทบาท", + "重置当前角色": "รีเซ็ตบทบาทปัจจุบัน", + "已重置角色配置": "รีเซ็ตการกำหนดค่าบทบาทแล้ว", + "成本价": "ราคาทุน", + "供应商申请": "คําขอผู้จัดจําหน่าย", + "供应商-申请": "ผู้จัดจําหน่าย - คําขอ", + "供应商-渠道管理": "ผู้จัดจําหน่าย - การจัดการช่องทาง", + "供应商渠道管理": "การจัดการช่องทางผู้จัดจําหน่าย", + "供应商-定价设置": "ผู้จัดจําหน่าย - การตั้งราคา", + "供应商定价设置": "การตั้งราคาผู้จัดจําหน่าย", + "供应商-数据看板": "ผู้จัดจําหน่าย - แดชบอร์ด", + "供应商数据看板": "แดชบอร์ดผู้จัดจําหน่าย", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index e1a6ec3..690f91f 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -5497,6 +5497,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "Mặc định là ap-guangzhou, có thể thay đổi thành ap-beijing, v.v.", "(共 {{total}} 个)": "(Tổng cộng {{total}})", "(共 {{total}} 个,省略 {{omit}} 个)": "(Tổng số mục {{total}}, đã bỏ qua mục {{omit}})", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Hiện hỗ trợ cấu hình Yipay/Yipay và sử dụng địa chỉ máy chủ ở trên làm địa chỉ gọi lại theo mặc định!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(Hiện hỗ trợ cấu hình Yipay/Yipay và sử dụng địa chỉ máy chủ ở trên làm địa chỉ gọi lại theo mặc định!)", + "热度配置": "Cau hinh nhiet", + "模型热度排行配置": "Cau hinh xep hang nhiet mo hinh", + "热度分 = (手动基数 + 实际调用) x 权重": "Diem nhiet = (Co so thu cong + Cuoc goi thuc te) x Trong so", + "预估热度分": "Diem nhiet uoc tinh", + "统计周期": "Chu ky thong ke", + "统计周期设置": "Cai dat chu ky thong ke", + "统计周期已更新,重新加载数据中...": "Chu ky da cap nhat, dang tai lai du lieu...", + "权重配置": "Cau hinh trong so", + "手动调整": "Dieu chinh thu cong", + "实际调用": "Cuoc goi thuc te", + "全部历史": "Tat ca lich su", + "近 7 天": "7 ngay qua", + "近 30 天": "30 ngay qua", + "模型 / 渠道": "Mo hinh / Kenh", + "搜索渠道": "Tim kenh", + "不修改": "Khong thay doi", + "批量保存成功": "Luu hang loat thanh cong", + "批量保存失败": "Luu hang loat that bai", + "序号": "STT", + "热门": "Pho bien", + "操作记录": "Nhat ky thao tac", + "系统操作日志审计": "Kiem tra nhat ky thao tac he thong", + "操作详情": "Chi tiet thao tac", + "操作描述": "Mo ta thao tac", + "操作者": "Nguoi thao tac", + "资源ID": "ID tai nguyen", + "资源类型": "Loai tai nguyen", + "资源名称": "Ten tai nguyen", + "IP地址": "Dia chi IP", + "兑换码": "Ma doi thuong", + "角色菜单权限配置": "Cau hinh quyen menu vai tro", + "重置当前角色": "Dat lai vai tro hien tai", + "已重置角色配置": "Cau hinh vai tro da duoc dat lai", + "成本价": "Gia von", + "供应商申请": "Don xin nha cung cap", + "供应商-申请": "Nha cung cap - Don xin", + "供应商-渠道管理": "Nha cung cap - Quan ly kenh", + "供应商渠道管理": "Quan ly kenh nha cung cap", + "供应商-定价设置": "Nha cung cap - Cai dat gia", + "供应商定价设置": "Cai dat gia nha cung cap", + "供应商-数据看板": "Nha cung cap - Bang dieu khien", + "供应商数据看板": "Bang dieu khien nha cung cap", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 847d9b1..46e15cf 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -5445,6 +5445,93 @@ "(共 {{total}} 个)": "(共 {{total}} 个)", "(共 {{total}} 个,省略 {{omit}} 个)": "(共 {{total}} 个,省略 {{omit}} 个)", "(含上次测试结果)": "(含上次测试结果)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)", + "热度配置": "热度配置", + "模型热度排行配置": "模型热度排行配置", + "热度分 = (手动基数 + 实际调用) x 权重": "热度分 = (手动基数 + 实际调用) x 权重", + "预估热度分": "预估热度分", + "统计周期": "统计周期", + "统计周期设置": "统计周期设置", + "统计周期已更新,重新加载数据中...": "统计周期已更新,重新加载数据中...", + "权重配置": "权重配置", + "手动调整": "手动调整", + "实际调用": "实际调用", + "全部历史": "全部历史", + "近 7 天": "近 7 天", + "近 30 天": "近 30 天", + "模型 / 渠道": "模型 / 渠道", + "搜索渠道": "搜索渠道", + "不修改": "不修改", + "批量保存成功": "批量保存成功", + "批量保存失败": "批量保存失败", + "序号": "序号", + "热门": "热门", + "操作记录": "操作记录", + "系统操作日志审计": "系统操作日志审计", + "操作详情": "操作详情", + "操作描述": "操作描述", + "操作者": "操作者", + "资源ID": "资源ID", + "资源类型": "资源类型", + "资源名称": "资源名称", + "IP地址": "IP地址", + "兑换码": "兑换码", + "角色菜单权限配置": "角色菜单权限配置", + "重置当前角色": "重置当前角色", + "已重置角色配置": "已重置角色配置", + "成本价": "成本价", + "供应商申请": "供应商申请", + "供应商-申请": "供应商-申请", + "供应商-渠道管理": "供应商-渠道管理", + "供应商渠道管理": "供应商渠道管理", + "供应商-定价设置": "供应商-定价设置", + "供应商定价设置": "供应商定价设置", + "供应商-数据看板": "供应商-数据看板", + "供应商数据看板": "供应商数据看板", + "支付宝 (Alipay) 设置": "支付宝 (Alipay) 设置", + "支付宝 App ID": "支付宝 App ID", + "支付宝开放平台应用的 APPID": "支付宝开放平台应用的 APPID", + "商户私钥": "商户私钥", + "应用私钥,PKCS8 格式的 RSA2 私钥": "应用私钥,PKCS8 格式的 RSA2 私钥", + "支付宝公钥": "支付宝公钥", + "支付宝开放平台获取的支付宝公钥": "支付宝开放平台获取的支付宝公钥", + "自定义异步通知地址": "自定义异步通知地址", + "自定义同步跳转地址": "自定义同步跳转地址", + "单价(元/额度)": "单价(元/额度)", + "例如:1,就是1元/额度": "例如:1,就是1元/额度", + "最低充值额度": "最低充值额度", + "例如:1": "例如:1", + "沙箱模式": "沙箱模式", + "沙箱 App ID": "沙箱 App ID", + "沙箱环境的 APPID": "沙箱环境的 APPID", + "沙箱商户私钥": "沙箱商户私钥", + "沙箱环境的应用私钥": "沙箱环境的应用私钥", + "沙箱环境": "沙箱环境", + "支付宝开放平台": "支付宝开放平台", + "启用支付宝支付": "启用支付宝支付", + "更新支付宝设置": "更新支付宝设置", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。", + "异步通知地址 (Notify URL)": "异步通知地址 (Notify URL)", + "启用微信支付": "启用微信支付", + "微信支付 (WeChat Pay) 设置": "微信支付 (WeChat Pay) 设置", + "微信公众号/小程序 AppID": "微信公众号/小程序 AppID", + "微信开放平台的 AppID": "微信开放平台的 AppID", + "商户号 (MchID)": "商户号 (MchID)", + "微信支付商户号": "微信支付商户号", + "APIv3 密钥": "APIv3 密钥", + "APIv3 密钥,32位字符": "APIv3 密钥,32位字符", + "商户 API 证书的私钥 (PKCS8 PEM)": "商户 API 证书的私钥 (PKCS8 PEM)", + "证书序列号": "证书序列号", + "商户 API 证书序列号": "商户 API 证书序列号", + "微信支付商户平台": "微信支付商户平台", + "API v3 文档": "API v3 文档", + "更新微信支付设置": "更新微信支付设置", + "沙箱商户号": "沙箱商户号", + "沙箱环境的商户号": "沙箱环境的商户号", + "沙箱 APIv3 密钥": "沙箱 APIv3 密钥", + "沙箱环境 APIv3 密钥": "沙箱环境 APIv3 密钥", + "沙箱环境商户私钥": "沙箱环境商户私钥", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "请确保已配置 APIv3 密钥,并下载商户证书序列号。", + "可选,留空则使用默认地址": "可选,留空则使用默认地址" } -} +} \ No newline at end of file diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index f3225fc..06bcb80 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -5496,6 +5496,93 @@ "默认 ap-guangzhou,可改为 ap-beijing 等": "預設 ap-guangzhou,可改為 ap-beijing 等", "(共 {{total}} 个)": "(共 {{total}} 個)", "(共 {{total}} 个,省略 {{omit}} 个)": "(共 {{total}} 個,省略 {{omit}} 個)", - "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(目前支援易付款/Yipay 配置,預設使用上方伺服器位址作為回呼位址!)" + "(当前支持易支付/Yipay 配置,默认使用上方服务器地址作为回调地址!)": "(目前支援易付款/Yipay 配置,預設使用上方伺服器位址作為回呼位址!)", + "热度配置": "熱度配置", + "模型热度排行配置": "模型熱度排行配置", + "热度分 = (手动基数 + 实际调用) x 权重": "熱度分 = (手動基數 + 實際調用) x 權重", + "预估热度分": "預估熱度分", + "统计周期": "統計週期", + "统计周期设置": "統計週期設置", + "统计周期已更新,重新加载数据中...": "統計週期已更新,重新加載數據中...", + "权重配置": "權重配置", + "手动调整": "手動調整", + "实际调用": "實際調用", + "全部历史": "全部歷史", + "近 7 天": "近 7 天", + "近 30 天": "近 30 天", + "模型 / 渠道": "模型 / 渠道", + "搜索渠道": "搜索渠道", + "不修改": "不修改", + "批量保存成功": "批量保存成功", + "批量保存失败": "批量保存失敗", + "序号": "序號", + "热门": "熱門", + "操作记录": "操作記錄", + "系统操作日志审计": "系統操作日誌審計", + "操作详情": "操作詳情", + "操作描述": "操作描述", + "操作者": "操作者", + "资源ID": "資源ID", + "资源类型": "資源類型", + "资源名称": "資源名稱", + "IP地址": "IP地址", + "兑换码": "兌換碼", + "角色菜单权限配置": "角色菜單權限配置", + "重置当前角色": "重置當前角色", + "已重置角色配置": "已重置角色配置", + "成本价": "成本價", + "供应商申请": "供應商申請", + "供应商-申请": "供應商-申請", + "供应商-渠道管理": "供應商-渠道管理", + "供应商渠道管理": "供應商渠道管理", + "供应商-定价设置": "供應商-定價設置", + "供应商定价设置": "供應商定價設置", + "供应商-数据看板": "供應商-數據看板", + "供应商数据看板": "供應商數據看板", + "支付宝 (Alipay) 设置": "Alipay Settings", + "支付宝 App ID": "Alipay App ID", + "支付宝开放平台应用的 APPID": "APPID of the Alipay Open Platform application", + "商户私钥": "Merchant Private Key", + "应用私钥,PKCS8 格式的 RSA2 私钥": "Application private key, RSA2 private key in PKCS8 format", + "支付宝公钥": "Alipay Public Key", + "支付宝开放平台获取的支付宝公钥": "Alipay public key obtained from Alipay Open Platform", + "自定义异步通知地址": "Custom Async Notify URL", + "自定义同步跳转地址": "Custom Sync Return URL", + "单价(元/额度)": "Unit Price (CNY/Quota)", + "例如:1,就是1元/额度": "e.g. 1 means 1 CNY per quota unit", + "最低充值额度": "Minimum Top-Up Amount", + "例如:1": "e.g. 1", + "沙箱模式": "Sandbox Mode", + "沙箱 App ID": "Sandbox App ID", + "沙箱环境的 APPID": "APPID for sandbox environment", + "沙箱商户私钥": "Sandbox Merchant Private Key", + "沙箱环境的应用私钥": "Application private key for sandbox environment", + "沙箱环境": "Sandbox Environment", + "支付宝开放平台": "Alipay Open Platform", + "启用支付宝支付": "Enable Alipay Payment", + "更新支付宝设置": "Update Alipay Settings", + "请确保已在支付宝开放平台配置 RSA2 密钥,并设置签约产品为「电脑网站支付」。": "Please ensure RSA2 keys are configured on Alipay Open Platform and the signed product is set to \"Website Payment\".", + "异步通知地址 (Notify URL)": "Async Notify URL", + "启用微信支付": "Enable WeChat Pay", + "微信支付 (WeChat Pay) 设置": "WeChat Pay Settings", + "微信公众号/小程序 AppID": "WeChat Official Account/Mini Program AppID", + "微信开放平台的 AppID": "AppID from WeChat Open Platform", + "商户号 (MchID)": "Merchant ID (MchID)", + "微信支付商户号": "WeChat Pay Merchant ID", + "APIv3 密钥": "APIv3 Key", + "APIv3 密钥,32位字符": "APIv3 key, 32 characters", + "商户 API 证书的私钥 (PKCS8 PEM)": "Merchant API Certificate Private Key (PKCS8 PEM)", + "证书序列号": "Certificate Serial Number", + "商户 API 证书序列号": "Merchant API Certificate Serial Number", + "微信支付商户平台": "WeChat Pay Merchant Platform", + "API v3 文档": "API v3 Documentation", + "更新微信支付设置": "Update WeChat Pay Settings", + "沙箱商户号": "Sandbox Merchant ID", + "沙箱环境的商户号": "Merchant ID for sandbox environment", + "沙箱 APIv3 密钥": "Sandbox APIv3 Key", + "沙箱环境 APIv3 密钥": "APIv3 key for sandbox environment", + "沙箱环境商户私钥": "Sandbox Merchant Private Key", + "请确保已配置 APIv3 密钥,并下载商户证书序列号。": "Please ensure the APIv3 key is configured and the merchant certificate serial number is downloaded.", + "可选,留空则使用默认地址": "Optional, leave blank to use default URL" } } \ No newline at end of file diff --git a/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx b/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx index ce336fe..8dbfc07 100644 --- a/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx +++ b/web/src/pages/Setting/Operation/SettingsSidebarModulesAdmin.jsx @@ -336,9 +336,9 @@ export default function SettingsSidebarModulesAdmin(props) { value={selectedRole} onChange={(e) => setSelectedRole(e.target.value)} > - {USER_ROLE_NAMES[USER_ROLES.USER]} - {USER_ROLE_NAMES[USER_ROLES.ADMIN]} - {USER_ROLE_NAMES[USER_ROLES.ROOT]} + {t(USER_ROLE_NAMES[USER_ROLES.USER])} + {t(USER_ROLE_NAMES[USER_ROLES.ADMIN])} + {t(USER_ROLE_NAMES[USER_ROLES.ROOT])} diff --git a/web/src/pages/Setting/Payment/SettingsPaymentGatewayAlipay.jsx b/web/src/pages/Setting/Payment/SettingsPaymentGatewayAlipay.jsx new file mode 100644 index 0000000..745b95a --- /dev/null +++ b/web/src/pages/Setting/Payment/SettingsPaymentGatewayAlipay.jsx @@ -0,0 +1,308 @@ +import React, { useEffect, useState, useRef } from "react"; +import { + Banner, + Button, + Form, + Row, + Col, + Typography, + Spin, +} from "@douyinfe/semi-ui"; +const { Text } = Typography; +import { + API, + removeTrailingSlash, + showError, + showSuccess, +} from "../../../helpers"; +import { useTranslation } from "react-i18next"; + +export default function SettingsPaymentGatewayAlipay(props) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [inputs, setInputs] = useState({ + AlipayEnabled: false, + AlipayAppId: "", + AlipayPrivateKey: "", + AlipayPublicKey: "", + AlipayNotifyUrl: "", + AlipayReturnUrl: "", + AlipayUnitPrice: 1.0, + AlipayMinTopUp: 1, + AlipaySandbox: false, + AlipaySandboxAppId: "", + AlipaySandboxPrivateKey: "", + }); + const [originInputs, setOriginInputs] = useState({}); + const formApiRef = useRef(null); + + useEffect(() => { + if (props.options && formApiRef.current) { + const currentInputs = { + AlipayEnabled: props.options.AlipayEnabled || false, + AlipayAppId: props.options.AlipayAppId || "", + AlipayPrivateKey: props.options.AlipayPrivateKey || "", + AlipayPublicKey: props.options.AlipayPublicKey || "", + AlipayNotifyUrl: props.options.AlipayNotifyUrl || "", + AlipayReturnUrl: props.options.AlipayReturnUrl || "", + AlipayUnitPrice: + props.options.AlipayUnitPrice !== undefined + ? parseFloat(props.options.AlipayUnitPrice) + : 1.0, + AlipayMinTopUp: + props.options.AlipayMinTopUp !== undefined + ? parseInt(props.options.AlipayMinTopUp) + : 1, + AlipaySandbox: props.options.AlipaySandbox || false, + AlipaySandboxAppId: props.options.AlipaySandboxAppId || "", + AlipaySandboxPrivateKey: props.options.AlipaySandboxPrivateKey || "", + }; + setInputs(currentInputs); + setOriginInputs({ ...currentInputs }); + formApiRef.current.setValues(currentInputs); + } + }, [props.options]); + + const handleFormChange = (values) => { + setInputs(values); + }; + + const submitAlipaySetting = async () => { + if (props.options.ServerAddress === "") { + showError(t("请先填写服务器地址")); + return; + } + + setLoading(true); + try { + const options = []; + + options.push({ + key: "AlipayEnabled", + value: inputs.AlipayEnabled ? "true" : "false", + }); + if (inputs.AlipayAppId !== "") { + options.push({ key: "AlipayAppId", value: inputs.AlipayAppId }); + } + if (inputs.AlipayPrivateKey !== "") { + options.push({ + key: "AlipayPrivateKey", + value: inputs.AlipayPrivateKey, + }); + } + if (inputs.AlipayPublicKey !== "") { + options.push({ + key: "AlipayPublicKey", + value: inputs.AlipayPublicKey, + }); + } + if (inputs.AlipayNotifyUrl !== "") { + options.push({ + key: "AlipayNotifyUrl", + value: inputs.AlipayNotifyUrl, + }); + } + if (inputs.AlipayReturnUrl !== "") { + options.push({ + key: "AlipayReturnUrl", + value: inputs.AlipayReturnUrl, + }); + } + options.push({ + key: "AlipayUnitPrice", + value: inputs.AlipayUnitPrice.toString(), + }); + options.push({ + key: "AlipayMinTopUp", + value: inputs.AlipayMinTopUp.toString(), + }); + options.push({ + key: "AlipaySandbox", + value: inputs.AlipaySandbox ? "true" : "false", + }); + if (inputs.AlipaySandboxAppId !== "") { + options.push({ + key: "AlipaySandboxAppId", + value: inputs.AlipaySandboxAppId, + }); + } + if (inputs.AlipaySandboxPrivateKey !== "") { + options.push({ + key: "AlipaySandboxPrivateKey", + value: inputs.AlipaySandboxPrivateKey, + }); + } + + const requestQueue = options.map((opt) => + API.put("/api/option/", { + key: opt.key, + value: opt.value, + }) + ); + + const results = await Promise.all(requestQueue); + const errorResults = results.filter((res) => !res.data.success); + if (errorResults.length > 0) { + errorResults.forEach((res) => { + showError(res.data.message); + }); + } else { + showSuccess(t("更新成功")); + setOriginInputs({ ...inputs }); + props.refresh?.(); + } + } catch (error) { + showError(t("更新失败")); + } + setLoading(false); + }; + + return ( + +
(formApiRef.current = api)} + > + + + {t( + "支付宝支付集成说明:请先在支付宝开放平台创建应用并获取以下配置信息。" + )} +
+ + {t("支付宝开放平台")} + + {" | "} + + {t("沙箱环境")} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ); +} diff --git a/web/src/pages/Setting/Payment/SettingsPaymentGatewayWechat.jsx b/web/src/pages/Setting/Payment/SettingsPaymentGatewayWechat.jsx new file mode 100644 index 0000000..5a37216 --- /dev/null +++ b/web/src/pages/Setting/Payment/SettingsPaymentGatewayWechat.jsx @@ -0,0 +1,351 @@ +import React, { useEffect, useState, useRef } from "react"; +import { + Banner, + Button, + Form, + Row, + Col, + Typography, + Spin, +} from "@douyinfe/semi-ui"; +const { Text } = Typography; +import { + API, + removeTrailingSlash, + showError, + showSuccess, +} from "../../../helpers"; +import { useTranslation } from "react-i18next"; + +export default function SettingsPaymentGatewayWechat(props) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [inputs, setInputs] = useState({ + WechatPayEnabled: false, + WechatPayAppId: "", + WechatPayMchId: "", + WechatPayApiV3Key: "", + WechatPayPrivateKey: "", + WechatPayCertSerial: "", + WechatPayNotifyUrl: "", + WechatPayReturnUrl: "", + WechatPayUnitPrice: 1.0, + WechatPayMinTopUp: 1, + WechatPaySandbox: false, + WechatPaySandboxMchId: "", + WechatPaySandboxApiV3Key: "", + WechatPaySandboxPrivateKey: "", + }); + const [originInputs, setOriginInputs] = useState({}); + const formApiRef = useRef(null); + + useEffect(() => { + if (props.options && formApiRef.current) { + const currentInputs = { + WechatPayEnabled: props.options.WechatPayEnabled || false, + WechatPayAppId: props.options.WechatPayAppId || "", + WechatPayMchId: props.options.WechatPayMchId || "", + WechatPayApiV3Key: props.options.WechatPayApiV3Key || "", + WechatPayPrivateKey: props.options.WechatPayPrivateKey || "", + WechatPayCertSerial: props.options.WechatPayCertSerial || "", + WechatPayNotifyUrl: props.options.WechatPayNotifyUrl || "", + WechatPayReturnUrl: props.options.WechatPayReturnUrl || "", + WechatPayUnitPrice: + props.options.WechatPayUnitPrice !== undefined + ? parseFloat(props.options.WechatPayUnitPrice) + : 1.0, + WechatPayMinTopUp: + props.options.WechatPayMinTopUp !== undefined + ? parseInt(props.options.WechatPayMinTopUp) + : 1, + WechatPaySandbox: props.options.WechatPaySandbox || false, + WechatPaySandboxMchId: props.options.WechatPaySandboxMchId || "", + WechatPaySandboxApiV3Key: + props.options.WechatPaySandboxApiV3Key || "", + WechatPaySandboxPrivateKey: + props.options.WechatPaySandboxPrivateKey || "", + }; + setInputs(currentInputs); + setOriginInputs({ ...currentInputs }); + formApiRef.current.setValues(currentInputs); + } + }, [props.options]); + + const handleFormChange = (values) => { + setInputs(values); + }; + + const submitWechatPaySetting = async () => { + if (props.options.ServerAddress === "") { + showError(t("请先填写服务器地址")); + return; + } + + setLoading(true); + try { + const options = []; + + options.push({ + key: "WechatPayEnabled", + value: inputs.WechatPayEnabled ? "true" : "false", + }); + if (inputs.WechatPayAppId !== "") { + options.push({ key: "WechatPayAppId", value: inputs.WechatPayAppId }); + } + if (inputs.WechatPayMchId !== "") { + options.push({ key: "WechatPayMchId", value: inputs.WechatPayMchId }); + } + if (inputs.WechatPayApiV3Key !== "") { + options.push({ + key: "WechatPayApiV3Key", + value: inputs.WechatPayApiV3Key, + }); + } + if (inputs.WechatPayPrivateKey !== "") { + options.push({ + key: "WechatPayPrivateKey", + value: inputs.WechatPayPrivateKey, + }); + } + if (inputs.WechatPayCertSerial !== "") { + options.push({ + key: "WechatPayCertSerial", + value: inputs.WechatPayCertSerial, + }); + } + if (inputs.WechatPayNotifyUrl !== "") { + options.push({ + key: "WechatPayNotifyUrl", + value: inputs.WechatPayNotifyUrl, + }); + } + if (inputs.WechatPayReturnUrl !== "") { + options.push({ + key: "WechatPayReturnUrl", + value: inputs.WechatPayReturnUrl, + }); + } + options.push({ + key: "WechatPayUnitPrice", + value: inputs.WechatPayUnitPrice.toString(), + }); + options.push({ + key: "WechatPayMinTopUp", + value: inputs.WechatPayMinTopUp.toString(), + }); + options.push({ + key: "WechatPaySandbox", + value: inputs.WechatPaySandbox ? "true" : "false", + }); + if (inputs.WechatPaySandboxMchId !== "") { + options.push({ + key: "WechatPaySandboxMchId", + value: inputs.WechatPaySandboxMchId, + }); + } + if (inputs.WechatPaySandboxApiV3Key !== "") { + options.push({ + key: "WechatPaySandboxApiV3Key", + value: inputs.WechatPaySandboxApiV3Key, + }); + } + if (inputs.WechatPaySandboxPrivateKey !== "") { + options.push({ + key: "WechatPaySandboxPrivateKey", + value: inputs.WechatPaySandboxPrivateKey, + }); + } + + const requestQueue = options.map((opt) => + API.put("/api/option/", { + key: opt.key, + value: opt.value, + }) + ); + + const results = await Promise.all(requestQueue); + const errorResults = results.filter((res) => !res.data.success); + if (errorResults.length > 0) { + errorResults.forEach((res) => { + showError(res.data.message); + }); + } else { + showSuccess(t("更新成功")); + setOriginInputs({ ...inputs }); + props.refresh?.(); + } + } catch (error) { + showError(t("更新失败")); + } + setLoading(false); + }; + + return ( + +
(formApiRef.current = api)} + > + + + {t( + "微信支付集成说明:请先在微信支付商户平台申请接入,获取以下配置信息。" + )} +
+ + {t("微信支付商户平台")} + + {" | "} + + {t("API v3 文档")} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ); +}