diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04a64d10..f610f799 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@
* 功能优化:首次登录不需要验证码,直接登录,登录失败之后才弹出验证码
* 功能新增:给 AI 应用(角色)增加分类,前端支持分类筛选
* 功能优化:允许用户在聊天页面设置是否使用流式输出或者一次性输出,兼容 GPT-O1 模型。
+* 功能优化:移除PayJS支付渠道支持,PayJs已经关闭注册服务,请使用其他支付方式。
+* 功能新增:新增GeeK易支付支付渠道,支持支付宝,微信支付,QQ钱包,京东支付,抖音支付,Paypal支付等支付方式
## v4.1.3
* 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信
diff --git a/api/core/types/config.go b/api/core/types/config.go
index 9638f620..f6e59727 100644
--- a/api/core/types/config.go
+++ b/api/core/types/config.go
@@ -12,24 +12,23 @@ import (
)
type AppConfig struct {
- Path string `toml:"-"`
- Listen string
- Session Session
- AdminSession Session
- ProxyURL string
- MysqlDns string // mysql 连接地址
- StaticDir string // 静态资源目录
- StaticUrl string // 静态资源 URL
- Redis RedisConfig // redis 连接信息
- ApiConfig ApiConfig // ChatPlus API authorization configs
- SMS SMSConfig // send mobile message config
- OSS OSSConfig // OSS config
-
+ Path string `toml:"-"`
+ Listen string
+ Session Session
+ AdminSession Session
+ ProxyURL string
+ MysqlDns string // mysql 连接地址
+ StaticDir string // 静态资源目录
+ StaticUrl string // 静态资源 URL
+ Redis RedisConfig // redis 连接信息
+ ApiConfig ApiConfig // ChatPlus API authorization configs
+ SMS SMSConfig // send mobile message config
+ OSS OSSConfig // OSS config
+ SmtpConfig SmtpConfig // 邮件发送配置
XXLConfig XXLConfig
AlipayConfig AlipayConfig // 支付宝支付渠道配置
HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置
- SmtpConfig SmtpConfig // 邮件发送配置
- JPayConfig JPayConfig // payjs 支付配置
+ GeekPayConfig GeekPayConfig // GEEK 支付配置
WechatPayConfig WechatPayConfig // 微信支付渠道配置
TikaHost string // TiKa 服务器地址
}
@@ -83,10 +82,9 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置
ReturnURL string // 支付成功返回地址
}
-// JPayConfig PayJs 支付配置
-type JPayConfig struct {
+// GeekPayConfig GEEK支付配置
+type GeekPayConfig struct {
Enabled bool
- Name string // 支付名称,默认 wechat
AppId string // 商户 ID
PrivateKey string // 私钥
ApiURL string // API 网关
diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go
index 5d807f79..8a0112a9 100644
--- a/api/handler/payment_handler.go
+++ b/api/handler/payment_handler.go
@@ -19,7 +19,6 @@ import (
"geekai/utils"
"geekai/utils/resp"
"github.com/shopspring/decimal"
- "math"
"net/http"
"net/url"
"sync"
@@ -34,19 +33,12 @@ type PayWay struct {
Value string `json:"value"`
}
-var (
- PayWayAlipay = PayWay{Name: "支付宝", Value: "alipay"}
- PayWayXunHu = PayWay{Name: "虎皮椒", Value: "hupi"}
- PayWayJs = PayWay{Name: "PayJS", Value: "payjs"}
- PayWayWechat = PayWay{Name: "微信支付", Value: "wechat"}
-)
-
// PaymentHandler 支付服务回调 handler
type PaymentHandler struct {
BaseHandler
alipayService *payment.AlipayService
huPiPayService *payment.HuPiPayService
- jsPayService *payment.JPayService
+ geekPayService *payment.GeekPayService
wechatPayService *payment.WechatPayService
snowflake *service.Snowflake
fs embed.FS
@@ -58,7 +50,7 @@ func NewPaymentHandler(
server *core.AppServer,
alipayService *payment.AlipayService,
huPiPayService *payment.HuPiPayService,
- jsPayService *payment.JPayService,
+ geekPayService *payment.GeekPayService,
wechatPayService *payment.WechatPayService,
db *gorm.DB,
snowflake *service.Snowflake,
@@ -66,7 +58,7 @@ func NewPaymentHandler(
return &PaymentHandler{
alipayService: alipayService,
huPiPayService: huPiPayService,
- jsPayService: jsPayService,
+ geekPayService: geekPayService,
wechatPayService: wechatPayService,
snowflake: snowflake,
fs: fs,
@@ -81,10 +73,9 @@ func NewPaymentHandler(
func (h *PaymentHandler) DoPay(c *gin.Context) {
orderNo := h.GetTrim(c, "order_no")
- payWay := h.GetTrim(c, "pay_way")
t := h.GetInt(c, "t", 0)
sign := h.GetTrim(c, "sign")
- signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, payWay, t, h.signKey)
+ signStr := fmt.Sprintf("%s-%d-%s", orderNo, t, h.signKey)
newSign := utils.Sha256(signStr)
if newSign != sign {
resp.ERROR(c, "订单签名错误!")
@@ -118,7 +109,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
// 更新扫码状态
h.DB.Model(&order).UpdateColumn("status", types.OrderScanned)
- if payWay == "alipay" { // 支付宝
+ if order.PayWay == "alipay" { // 支付宝
amount := fmt.Sprintf("%.2f", order.Amount)
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject)
if err != nil {
@@ -128,7 +119,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
c.Redirect(302, uri)
return
- } else if payWay == "hupi" { // 虎皮椒支付
+ } else if order.PayWay == "hupi" { // 虎皮椒支付
params := payment.HuPiPayReq{
Version: "1.1",
TradeOrderId: orderNo,
@@ -144,15 +135,41 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
}
c.Redirect(302, r.URL)
+ } else if order.PayWay == "wechat" {
+ uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject)
+ if err != nil {
+ resp.ERROR(c, err.Error())
+ return
+ }
+
+ c.Redirect(302, uri)
+ } else if order.PayWay == "geek" {
+ params := payment.GeekPayParams{
+ OutTradeNo: orderNo,
+ Method: "web",
+ Name: order.Subject,
+ Money: fmt.Sprintf("%f", order.Amount),
+ ClientIP: c.ClientIP(),
+ Device: "pc",
+ Type: "alipay",
+ }
+
+ s, err := h.geekPayService.Pay(params)
+ if err != nil {
+ resp.ERROR(c, err.Error())
+ return
+ }
+ resp.SUCCESS(c, s)
}
- resp.ERROR(c, "Invalid operations")
+ //resp.ERROR(c, "Invalid operations")
}
// PayQrcode 生成支付 URL 二维码
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
var data struct {
- PayWay string `json:"pay_way"` // 支付方式
- ProductId uint `json:"product_id"`
+ PayWay string `json:"pay_way"` // 支付方式
+ PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq...
+ ProductId uint `json:"product_id"` // 支付产品ID
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -177,24 +194,22 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
return
}
- var payWay string
var notifyURL string
switch data.PayWay {
case "hupi":
- payWay = PayWayXunHu.Value
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
break
- case "payjs":
- payWay = PayWayJs.Value
- notifyURL = h.App.Config.JPayConfig.NotifyURL
+ case "geek":
+ notifyURL = h.App.Config.GeekPayConfig.NotifyURL
break
- case "alipay":
- payWay = PayWayAlipay.Value
+ case "alipay": // 支付宝商户支付
notifyURL = h.App.Config.AlipayConfig.NotifyURL
break
- default:
- payWay = PayWayWechat.Value
+ case "wechat": // 微信商户支付
notifyURL = h.App.Config.WechatPayConfig.NotifyURL
+ default:
+ resp.ERROR(c, "Invalid pay way")
+ return
}
// 创建订单
@@ -215,7 +230,8 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
- PayWay: payWay,
+ PayWay: data.PayWay,
+ PayType: data.PayType,
Remark: utils.JsonEncode(remark),
}
res = h.DB.Create(&order)
@@ -224,36 +240,26 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
return
}
- // PayJs 单独处理,只能用官方生成的二维码
- if data.PayWay == "payjs" {
- params := payment.JPayReq{
- TotalFee: int(math.Ceil(order.Amount * 100)),
- OutTradeNo: order.OrderNo,
- Subject: product.Name,
- }
- r := h.jsPayService.Pay(params)
- if r.IsOK() {
- resp.SUCCESS(c, gin.H{"order_no": order.OrderNo, "image": r.Qrcode})
- return
- } else {
- resp.ERROR(c, "error with generating payment qrcode: "+r.ReturnMsg)
- return
- }
- }
-
var logo string
- if data.PayWay == "alipay" {
+ switch data.PayType {
+ case "alipay":
logo = "res/img/alipay.jpg"
- } else if data.PayWay == "hupi" {
- if h.App.Config.HuPiPayConfig.Name == "wechat" {
- logo = "res/img/wechat-pay.jpg"
- } else {
- logo = "res/img/alipay.jpg"
- }
- } else if data.PayWay == "wechat" {
+ break
+ case "wechat":
+ logo = "res/img/wechat-pay.jpg"
+ break
+ case "qq":
+ logo = "res/img/qq-pay.jpg"
+ break
+ default:
+ logo = "res/img/geek-pay.jpg"
+
+ }
+ if data.PayType == "alipay" {
+ logo = "res/img/alipay.jpg"
+ } else if data.PayType == "wechat" {
logo = "res/img/wechat-pay.jpg"
}
-
file, err := h.fs.Open(logo)
if err != nil {
resp.ERROR(c, "error with open qrcode log file: "+err.Error())
@@ -268,31 +274,21 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
timestamp := time.Now().Unix()
signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey)
sign := utils.Sha256(signStr)
- var imageURL string
- if data.PayWay == "wechat" {
- payUrl, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(math.Floor(order.Amount*100)), product.Name)
- if err != nil {
- resp.ERROR(c, "error with generating wechat payment qrcode: "+err.Error())
- return
- } else {
- imageURL = payUrl
- }
- } else {
- imageURL = fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, timestamp, sign)
- }
- imgData, err := utils.GenQrcode(imageURL, 400, file)
+ payUrl := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&pay_type=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, data.PayType, timestamp, sign)
+ imgData, err := utils.GenQrcode(payUrl, 400, file)
if err != nil {
resp.ERROR(c, err.Error())
return
}
imgDataBase64 := base64.StdEncoding.EncodeToString(imgData)
- resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL})
+ resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": payUrl})
}
// Mobile 移动端支付
func (h *PaymentHandler) Mobile(c *gin.Context) {
var data struct {
- PayWay string `json:"pay_way"` // 支付方式
+ PayWay string `json:"pay_way"` // 支付方式
+ PayType string `json:"pay_type"` // 支付类别:wechat,alipay,qq...
ProductId uint `json:"product_id"`
}
if err := c.ShouldBindJSON(&data); err != nil {
@@ -319,12 +315,10 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
}
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
- var payWay string
var notifyURL, returnURL string
var payURL string
switch data.PayWay {
case "hupi":
- payWay = PayWayXunHu.Name
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL)
@@ -349,20 +343,16 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
return
}
payURL = r.URL
- case "payjs":
- payWay = PayWayJs.Name
- notifyURL = h.App.Config.JPayConfig.NotifyURL
- returnURL = h.App.Config.JPayConfig.ReturnURL
- totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart()
- params := url.Values{}
- params.Add("total_fee", fmt.Sprintf("%d", totalFee))
- params.Add("out_trade_no", orderNo)
- params.Add("body", product.Name)
- params.Add("notify_url", notifyURL)
- params.Add("auto", "0")
- payURL = h.jsPayService.PayH5(params)
+ case "geek":
+ //totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart()
+ //params := url.Values{}
+ //params.Add("total_fee", fmt.Sprintf("%d", totalFee))
+ //params.Add("out_trade_no", orderNo)
+ //params.Add("body", product.Name)
+ //params.Add("notify_url", notifyURL)
+ //params.Add("auto", "0")
+ //payURL = h.geekPayService.Pay(params)
case "alipay":
- payWay = PayWayAlipay.Name
payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name)
if err != nil {
errMsg := "error with generating Alipay URL: " + err.Error()
@@ -370,7 +360,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
return
}
case "wechat":
- payWay = PayWayWechat.Name
payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP())
if err != nil {
errMsg := "error with generating Wechat URL: " + err.Error()
@@ -399,7 +388,8 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
- PayWay: payWay,
+ PayWay: data.PayWay,
+ PayType: data.PayType,
Remark: utils.JsonEncode(remark),
}
res = h.DB.Create(&order)
@@ -506,20 +496,25 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
// GetPayWays 获取支付方式
func (h *PaymentHandler) GetPayWays(c *gin.Context) {
- data := gin.H{}
+ payWays := make([]gin.H, 0)
if h.App.Config.AlipayConfig.Enabled {
- data["alipay"] = gin.H{"name": "alipay"}
+ payWays = append(payWays, gin.H{"pay_way": "alipay", "pay_type": "alipay"})
}
if h.App.Config.HuPiPayConfig.Enabled {
- data["hupi"] = gin.H{"name": h.App.Config.HuPiPayConfig.Name}
+ payWays = append(payWays, gin.H{"pay_way": "hupi", "pay_type": h.App.Config.HuPiPayConfig.Name})
}
- if h.App.Config.JPayConfig.Enabled {
- data["payjs"] = gin.H{"name": h.App.Config.JPayConfig.Name}
+ if h.App.Config.GeekPayConfig.Enabled {
+ payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "alipay"})
+ payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wechat"})
+ payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qq"})
+ payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jd"})
+ payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"})
+ payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"})
}
if h.App.Config.WechatPayConfig.Enabled {
- data["wechat"] = gin.H{"name": "wechat"}
+ payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wechat"})
}
- resp.SUCCESS(c, data)
+ resp.SUCCESS(c, payWays)
}
// HuPiPayNotify 虎皮椒支付异步回调
@@ -593,12 +588,12 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
// 校验订单支付状态
tradeNo := c.Request.Form.Get("payjs_order_id")
- err = h.jsPayService.TradeVerify(tradeNo)
- if err != nil {
- logger.Error("订单校验失败:", err)
- c.String(http.StatusOK, "fail")
- return
- }
+ //err = h.geekPayService.TradeVerify(tradeNo)
+ //if err != nil {
+ // logger.Error("订单校验失败:", err)
+ // c.String(http.StatusOK, "fail")
+ // return
+ //}
err = h.notify(orderNo, tradeNo)
if err != nil {
diff --git a/api/handler/test_handler.go b/api/handler/test_handler.go
index fe38bc38..88e95a2f 100644
--- a/api/handler/test_handler.go
+++ b/api/handler/test_handler.go
@@ -11,10 +11,10 @@ import (
type TestHandler struct {
db *gorm.DB
snowflake *service.Snowflake
- js *payment.JPayService
+ js *payment.GeekPayService
}
-func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.JPayService) *TestHandler {
+func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.GeekPayService) *TestHandler {
return &TestHandler{db: db, snowflake: snowflake, js: js}
}
diff --git a/api/res/img/geek-pay.jpg b/api/res/img/geek-pay.jpg
new file mode 100644
index 00000000..48baf80d
Binary files /dev/null and b/api/res/img/geek-pay.jpg differ
diff --git a/api/res/img/qq-pay.jpg b/api/res/img/qq-pay.jpg
new file mode 100644
index 00000000..25a0b180
Binary files /dev/null and b/api/res/img/qq-pay.jpg differ
diff --git a/api/service/payment/geekpay_service.go b/api/service/payment/geekpay_service.go
new file mode 100644
index 00000000..2018f43f
--- /dev/null
+++ b/api/service/payment/geekpay_service.go
@@ -0,0 +1,136 @@
+package payment
+
+// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// * Copyright 2023 The Geek-AI Authors. All rights reserved.
+// * Use of this source code is governed by a Apache-2.0 license
+// * that can be found in the LICENSE file.
+// * @Author yangjian102621@163.com
+// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "fmt"
+ "geekai/core/types"
+ "io"
+ "net/http"
+ "net/url"
+ "sort"
+ "strings"
+ "time"
+)
+
+// GeekPayService Geek 支付服务
+type GeekPayService struct {
+ config *types.GeekPayConfig
+}
+
+func NewJPayService(appConfig *types.AppConfig) *GeekPayService {
+ return &GeekPayService{
+ config: &appConfig.GeekPayConfig,
+ }
+}
+
+type GeekPayParams struct {
+ Method string `json:"method"` // 接口类型
+ Device string `json:"device"` // 设备类型
+ Type string `json:"type"` // 支付方式
+ OutTradeNo string `json:"out_trade_no"` // 商户订单号
+ Name string `json:"name"` // 商品名称
+ Money string `json:"money"` // 商品金额
+ ClientIP string `json:"clientip"` //用户IP地址
+ SubOpenId string `json:"sub_openid"` // 微信用户 openid,仅小程序支付需要
+ SubAppId string `json:"sub_appid"` // 小程序 AppId,仅小程序支付需要
+}
+
+// Pay 支付订单
+func (s *GeekPayService) Pay(params GeekPayParams) (string, error) {
+ if params.Type == "wechat" {
+ params.Type = "wxpay"
+ }
+ p := map[string]string{
+ "pid": s.config.AppId,
+ "method": params.Method,
+ "device": params.Device,
+ "type": params.Type,
+ "out_trade_no": params.OutTradeNo,
+ "name": params.Name,
+ "money": params.Money,
+ "clientip": params.ClientIP,
+ "sub_openid": params.SubOpenId,
+ "sub_appid": params.SubAppId,
+ "notify_url": s.config.NotifyURL,
+ "return_url": s.config.ReturnURL,
+ "timestamp": fmt.Sprintf("%d", time.Now().Unix()),
+ }
+ sign, err := s.Sign(p)
+ if err != nil {
+ return "", err
+ }
+ p["sign"] = sign
+ p["sign_type"] = "RSA"
+ return s.sendRequest(s.config.ApiURL, p)
+}
+
+func (s *GeekPayService) Sign(params map[string]string) (string, error) {
+ // 按字母顺序排序参数
+ var keys []string
+ for k := range params {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ // 构建待签名字符串
+ var signStr strings.Builder
+ for _, k := range keys {
+ signStr.WriteString(k)
+ signStr.WriteString("=")
+ signStr.WriteString(params[k])
+ signStr.WriteString("&")
+ }
+ signString := strings.TrimSuffix(signStr.String(), "&")
+
+ // 使用RSA私钥签名
+ block, _ := pem.Decode([]byte(s.config.PrivateKey))
+ if block == nil {
+ return "", fmt.Errorf("failed to decode private key")
+ }
+
+ priKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return "", fmt.Errorf("failed to parse private key: %v", err)
+ }
+
+ hashed := sha256.Sum256([]byte(signString))
+ signature, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, hashed[:])
+ if err != nil {
+ panic(fmt.Sprintf("failed to sign: %v", err))
+ }
+
+ return base64.StdEncoding.EncodeToString(signature), nil
+}
+
+func (s *GeekPayService) sendRequest(apiEndpoint string, params map[string]string) (string, error) {
+ form := url.Values{}
+ for k, v := range params {
+ form.Add(k, v)
+ }
+
+ resp, err := http.PostForm(apiEndpoint, form)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+
+ return string(body), nil
+}
diff --git a/api/service/payment/payjs_service.go b/api/service/payment/payjs_service.go
deleted file mode 100644
index 56a98471..00000000
--- a/api/service/payment/payjs_service.go
+++ /dev/null
@@ -1,153 +0,0 @@
-package payment
-
-// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// * Copyright 2023 The Geek-AI Authors. All rights reserved.
-// * Use of this source code is governed by a Apache-2.0 license
-// * that can be found in the LICENSE file.
-// * @Author yangjian102621@163.com
-// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-import (
- "crypto/md5"
- "encoding/hex"
- "errors"
- "fmt"
- "geekai/core/types"
- "geekai/utils"
- "io"
- "net/http"
- "net/url"
- "sort"
- "strings"
-)
-
-type JPayService struct {
- config *types.JPayConfig
-}
-
-func NewJPayService(appConfig *types.AppConfig) *JPayService {
- return &JPayService{
- config: &appConfig.JPayConfig,
- }
-}
-
-type JPayReq struct {
- TotalFee int `json:"total_fee"`
- OutTradeNo string `json:"out_trade_no"`
- Subject string `json:"body"`
- NotifyURL string `json:"notify_url"`
- ReturnURL string `json:"callback_url"`
-}
-type JPayReps struct {
- OutTradeNo string `json:"out_trade_no"`
- OrderId string `json:"payjs_order_id"`
- ReturnCode int `json:"return_code"`
- ReturnMsg string `json:"return_msg"`
- Sign string `json:"Sign"`
- TotalFee string `json:"total_fee"`
- CodeUrl string `json:"code_url,omitempty"`
- Qrcode string `json:"qrcode,omitempty"`
-}
-
-func (r JPayReps) IsOK() bool {
- return r.ReturnMsg == "SUCCESS"
-}
-
-func (js *JPayService) Pay(param JPayReq) JPayReps {
- param.NotifyURL = js.config.NotifyURL
- var p = url.Values{}
- encode := utils.JsonEncode(param)
- m := make(map[string]interface{})
- _ = utils.JsonDecode(encode, &m)
- for k, v := range m {
- p.Add(k, fmt.Sprintf("%v", v))
- }
- p.Add("mchid", js.config.AppId)
-
- p.Add("sign", js.sign(p))
-
- cli := http.Client{}
- apiURL := fmt.Sprintf("%s/api/native", js.config.ApiURL)
- r, err := cli.PostForm(apiURL, p)
- if err != nil {
- return JPayReps{ReturnMsg: err.Error()}
- }
- defer r.Body.Close()
- bs, err := io.ReadAll(r.Body)
- if err != nil {
- return JPayReps{ReturnMsg: err.Error()}
- }
-
- var data JPayReps
- err = utils.JsonDecode(string(bs), &data)
- if err != nil {
- return JPayReps{ReturnMsg: err.Error()}
- }
- return data
-}
-
-func (js *JPayService) PayH5(p url.Values) string {
- p.Add("mchid", js.config.AppId)
- p.Add("sign", js.sign(p))
- return fmt.Sprintf("%s/api/cashier?%s", js.config.ApiURL, p.Encode())
-}
-
-func (js *JPayService) sign(params url.Values) string {
- params.Del(`sign`)
- var keys = make([]string, 0, 0)
- for key := range params {
- if params.Get(key) != `` {
- keys = append(keys, key)
- }
- }
- sort.Strings(keys)
-
- var pList = make([]string, 0, 0)
- for _, key := range keys {
- var value = strings.TrimSpace(params.Get(key))
- if len(value) > 0 {
- pList = append(pList, key+"="+value)
- }
- }
- var src = strings.Join(pList, "&")
- src += "&key=" + js.config.PrivateKey
-
- md5bs := md5.Sum([]byte(src))
- md5res := hex.EncodeToString(md5bs[:])
- return strings.ToUpper(md5res)
-}
-
-// TradeVerify 查询订单支付状态
-// @param tradeNo 支付平台交易 ID
-func (js *JPayService) TradeVerify(tradeNo string) error {
- apiURL := fmt.Sprintf("%s/api/check", js.config.ApiURL)
- params := url.Values{}
- params.Add("payjs_order_id", tradeNo)
- params.Add("sign", js.sign(params))
- data := strings.NewReader(params.Encode())
- resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", data)
- if err != nil {
- return fmt.Errorf("error with http reqeust: %v", err)
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return fmt.Errorf("error with reading response: %v", err)
- }
-
- var r struct {
- ReturnCode int `json:"return_code"`
- Status int `json:"status"`
- }
- err = utils.JsonDecode(string(body), &r)
- if err != nil {
- return fmt.Errorf("error with decode response: %v", err)
- }
-
- if r.ReturnCode == 1 && r.Status == 1 {
- return nil
- } else {
- logger.Errorf("PayJs 支付验证响应:%s", string(body))
- return errors.New("order not paid")
- }
-}
diff --git a/api/store/model/order.go b/api/store/model/order.go
index a1c6929f..86e81ca7 100644
--- a/api/store/model/order.go
+++ b/api/store/model/order.go
@@ -18,6 +18,7 @@ type Order struct {
Status types.OrderStatus
Remark string
PayTime int64
- PayWay string // 支付方式
+ PayWay string // 支付渠道
+ PayType string // 支付类型
DeletedAt gorm.DeletedAt
}
diff --git a/api/store/vo/order.go b/api/store/vo/order.go
index c076d002..39693649 100644
--- a/api/store/vo/order.go
+++ b/api/store/vo/order.go
@@ -16,5 +16,6 @@ type Order struct {
Status types.OrderStatus `json:"status"`
PayTime int64 `json:"pay_time"`
PayWay string `json:"pay_way"`
+ PayType string `json:"pay_type"`
Remark types.OrderRemark `json:"remark"`
}
diff --git a/database/update-v4.1.4.sql b/database/update-v4.1.4.sql
index 28d93e5c..0ea7f36f 100644
--- a/database/update-v4.1.4.sql
+++ b/database/update-v4.1.4.sql
@@ -11,4 +11,5 @@ ALTER TABLE `chatgpt_app_types`ADD PRIMARY KEY (`id`);
ALTER TABLE `chatgpt_app_types` MODIFY `id` int NOT NULL AUTO_INCREMENT;
ALTER TABLE `chatgpt_chat_roles` ADD `tid` INT NOT NULL COMMENT '分类ID' AFTER `name`;
-ALTER TABLE `chatgpt_chat_history` ADD `total_tokens` INT NOT NULL COMMENT '消耗总Token长度' AFTER `tokens`;
\ No newline at end of file
+ALTER TABLE `chatgpt_chat_history` ADD `total_tokens` INT NOT NULL COMMENT '消耗总Token长度' AFTER `tokens`;
+ALTER TABLE `chatgpt_orders` ADD `pay_type` VARCHAR(30) NOT NULL COMMENT '支付类型' AFTER `pay_way`;
\ No newline at end of file
diff --git a/web/src/assets/css/member.styl b/web/src/assets/css/member.styl
index c1f61bb5..9dab8325 100644
--- a/web/src/assets/css/member.styl
+++ b/web/src/assets/css/member.styl
@@ -110,6 +110,7 @@
overflow hidden
cursor pointer
transition: all 0.3s ease; /* 添加过渡效果 */
+ margin-bottom 20px
.image-container {
display flex
@@ -175,10 +176,11 @@
.pay-way {
padding 10px 0
display flex
- justify-content: space-between
+ justify-content: center
+ flex-wrap wrap
- .iconfont {
- margin-right 5px
+ .el-button {
+ margin 10px 5px 0 5px
}
}
}
diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css
index 31901bb5..a0959ced 100644
--- a/web/src/assets/iconfont/iconfont.css
+++ b/web/src/assets/iconfont/iconfont.css
@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4125778 */
- src: url('iconfont.woff2?t=1725929120246') format('woff2'),
- url('iconfont.woff?t=1725929120246') format('woff'),
- url('iconfont.ttf?t=1725929120246') format('truetype');
+ src: url('iconfont.woff2?t=1726612860394') format('woff2'),
+ url('iconfont.woff?t=1726612860394') format('woff'),
+ url('iconfont.ttf?t=1726612860394') format('truetype');
}
.iconfont {
@@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale;
}
+.icon-douyin:before {
+ content: "\e852";
+}
+
+.icon-paypal:before {
+ content: "\e60f";
+}
+
+.icon-qq:before {
+ content: "\e69f";
+}
+
+.icon-jd-pay:before {
+ content: "\e8dd";
+}
+
.icon-luma:before {
content: "\e704";
}
diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js
index ad94b5e2..32973473 100644
--- a/web/src/assets/iconfont/iconfont.js
+++ b/web/src/assets/iconfont/iconfont.js
@@ -1 +1 @@
-window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var t,h,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}t=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),t()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(i=t,o=a.document,z=!1,p(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,s())})}function s(){z||(z=!0,i())}function p(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(p,50)}s()}})(window);
\ No newline at end of file
+window._iconfont_svg_string_4125778='',(a=>{var l=(c=(c=document.getElementsByTagName("script"))[c.length-1]).getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var h,t,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(l&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(i=h,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}p()}})(window);
\ No newline at end of file
diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json
index f114daa2..7dc3877f 100644
--- a/web/src/assets/iconfont/iconfont.json
+++ b/web/src/assets/iconfont/iconfont.json
@@ -5,6 +5,34 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
+ {
+ "icon_id": "22174321",
+ "name": "抖音支付",
+ "font_class": "douyin",
+ "unicode": "e852",
+ "unicode_decimal": 59474
+ },
+ {
+ "icon_id": "1238433",
+ "name": "social-paypal",
+ "font_class": "paypal",
+ "unicode": "e60f",
+ "unicode_decimal": 58895
+ },
+ {
+ "icon_id": "1244217",
+ "name": "qq",
+ "font_class": "qq",
+ "unicode": "e69f",
+ "unicode_decimal": 59039
+ },
+ {
+ "icon_id": "18166714",
+ "name": "京东支付",
+ "font_class": "jd-pay",
+ "unicode": "e8dd",
+ "unicode_decimal": 59613
+ },
{
"icon_id": "41645421",
"name": "luma-logo",
diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf
index f00663ec..8b228359 100644
Binary files a/web/src/assets/iconfont/iconfont.ttf and b/web/src/assets/iconfont/iconfont.ttf differ
diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff
index ff63b66a..426fce0c 100644
Binary files a/web/src/assets/iconfont/iconfont.woff and b/web/src/assets/iconfont/iconfont.woff differ
diff --git a/web/src/assets/iconfont/iconfont.woff2 b/web/src/assets/iconfont/iconfont.woff2
index a6df0ee8..906588c2 100644
Binary files a/web/src/assets/iconfont/iconfont.woff2 and b/web/src/assets/iconfont/iconfont.woff2 differ
diff --git a/web/src/views/Member.vue b/web/src/views/Member.vue
index fcf359f9..11412a2e 100644
--- a/web/src/views/Member.vue
+++ b/web/src/views/Member.vue
@@ -36,57 +36,63 @@
-