add geek payment

This commit is contained in:
RockYang 2024-09-18 07:03:46 +08:00
parent 603bfa7def
commit 158db83965
19 changed files with 355 additions and 391 deletions

View File

@ -5,6 +5,8 @@
* 功能优化:首次登录不需要验证码,直接登录,登录失败之后才弹出验证码 * 功能优化:首次登录不需要验证码,直接登录,登录失败之后才弹出验证码
* 功能新增:给 AI 应用(角色)增加分类,前端支持分类筛选 * 功能新增:给 AI 应用(角色)增加分类,前端支持分类筛选
* 功能优化:允许用户在聊天页面设置是否使用流式输出或者一次性输出,兼容 GPT-O1 模型。 * 功能优化:允许用户在聊天页面设置是否使用流式输出或者一次性输出,兼容 GPT-O1 模型。
* 功能优化移除PayJS支付渠道支持PayJs已经关闭注册服务请使用其他支付方式。
* 功能新增新增GeeK易支付支付渠道支持支付宝微信支付QQ钱包京东支付抖音支付Paypal支付等支付方式
## v4.1.3 ## v4.1.3
* 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信 * 功能优化:重构用户登录模块,给所有的登录组件增加行为验证码功能,支持用户绑定手机,邮箱和微信

View File

@ -24,12 +24,11 @@ type AppConfig struct {
ApiConfig ApiConfig // ChatPlus API authorization configs ApiConfig ApiConfig // ChatPlus API authorization configs
SMS SMSConfig // send mobile message config SMS SMSConfig // send mobile message config
OSS OSSConfig // OSS config OSS OSSConfig // OSS config
SmtpConfig SmtpConfig // 邮件发送配置
XXLConfig XXLConfig XXLConfig XXLConfig
AlipayConfig AlipayConfig // 支付宝支付渠道配置 AlipayConfig AlipayConfig // 支付宝支付渠道配置
HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置 HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置
SmtpConfig SmtpConfig // 邮件发送配置 GeekPayConfig GeekPayConfig // GEEK 支付配置
JPayConfig JPayConfig // payjs 支付配置
WechatPayConfig WechatPayConfig // 微信支付渠道配置 WechatPayConfig WechatPayConfig // 微信支付渠道配置
TikaHost string // TiKa 服务器地址 TikaHost string // TiKa 服务器地址
} }
@ -83,10 +82,9 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置
ReturnURL string // 支付成功返回地址 ReturnURL string // 支付成功返回地址
} }
// JPayConfig PayJs 支付配置 // GeekPayConfig GEEK支付配置
type JPayConfig struct { type GeekPayConfig struct {
Enabled bool Enabled bool
Name string // 支付名称,默认 wechat
AppId string // 商户 ID AppId string // 商户 ID
PrivateKey string // 私钥 PrivateKey string // 私钥
ApiURL string // API 网关 ApiURL string // API 网关

View File

@ -19,7 +19,6 @@ import (
"geekai/utils" "geekai/utils"
"geekai/utils/resp" "geekai/utils/resp"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"math"
"net/http" "net/http"
"net/url" "net/url"
"sync" "sync"
@ -34,19 +33,12 @@ type PayWay struct {
Value string `json:"value"` 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 // PaymentHandler 支付服务回调 handler
type PaymentHandler struct { type PaymentHandler struct {
BaseHandler BaseHandler
alipayService *payment.AlipayService alipayService *payment.AlipayService
huPiPayService *payment.HuPiPayService huPiPayService *payment.HuPiPayService
jsPayService *payment.JPayService geekPayService *payment.GeekPayService
wechatPayService *payment.WechatPayService wechatPayService *payment.WechatPayService
snowflake *service.Snowflake snowflake *service.Snowflake
fs embed.FS fs embed.FS
@ -58,7 +50,7 @@ func NewPaymentHandler(
server *core.AppServer, server *core.AppServer,
alipayService *payment.AlipayService, alipayService *payment.AlipayService,
huPiPayService *payment.HuPiPayService, huPiPayService *payment.HuPiPayService,
jsPayService *payment.JPayService, geekPayService *payment.GeekPayService,
wechatPayService *payment.WechatPayService, wechatPayService *payment.WechatPayService,
db *gorm.DB, db *gorm.DB,
snowflake *service.Snowflake, snowflake *service.Snowflake,
@ -66,7 +58,7 @@ func NewPaymentHandler(
return &PaymentHandler{ return &PaymentHandler{
alipayService: alipayService, alipayService: alipayService,
huPiPayService: huPiPayService, huPiPayService: huPiPayService,
jsPayService: jsPayService, geekPayService: geekPayService,
wechatPayService: wechatPayService, wechatPayService: wechatPayService,
snowflake: snowflake, snowflake: snowflake,
fs: fs, fs: fs,
@ -81,10 +73,9 @@ func NewPaymentHandler(
func (h *PaymentHandler) DoPay(c *gin.Context) { func (h *PaymentHandler) DoPay(c *gin.Context) {
orderNo := h.GetTrim(c, "order_no") orderNo := h.GetTrim(c, "order_no")
payWay := h.GetTrim(c, "pay_way")
t := h.GetInt(c, "t", 0) t := h.GetInt(c, "t", 0)
sign := h.GetTrim(c, "sign") 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) newSign := utils.Sha256(signStr)
if newSign != sign { if newSign != sign {
resp.ERROR(c, "订单签名错误!") resp.ERROR(c, "订单签名错误!")
@ -118,7 +109,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
// 更新扫码状态 // 更新扫码状态
h.DB.Model(&order).UpdateColumn("status", types.OrderScanned) h.DB.Model(&order).UpdateColumn("status", types.OrderScanned)
if payWay == "alipay" { // 支付宝 if order.PayWay == "alipay" { // 支付宝
amount := fmt.Sprintf("%.2f", order.Amount) amount := fmt.Sprintf("%.2f", order.Amount)
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject) uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject)
if err != nil { if err != nil {
@ -128,7 +119,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
c.Redirect(302, uri) c.Redirect(302, uri)
return return
} else if payWay == "hupi" { // 虎皮椒支付 } else if order.PayWay == "hupi" { // 虎皮椒支付
params := payment.HuPiPayReq{ params := payment.HuPiPayReq{
Version: "1.1", Version: "1.1",
TradeOrderId: orderNo, TradeOrderId: orderNo,
@ -144,15 +135,41 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
} }
c.Redirect(302, r.URL) 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
} }
resp.ERROR(c, "Invalid operations")
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")
} }
// PayQrcode 生成支付 URL 二维码 // PayQrcode 生成支付 URL 二维码
func (h *PaymentHandler) PayQrcode(c *gin.Context) { func (h *PaymentHandler) PayQrcode(c *gin.Context) {
var data struct { var data struct {
PayWay string `json:"pay_way"` // 支付方式 PayWay string `json:"pay_way"` // 支付方式
ProductId uint `json:"product_id"` PayType string `json:"pay_type"` // 支付类别wechat,alipay,qq...
ProductId uint `json:"product_id"` // 支付产品ID
} }
if err := c.ShouldBindJSON(&data); err != nil { if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs) resp.ERROR(c, types.InvalidArgs)
@ -177,24 +194,22 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
return return
} }
var payWay string
var notifyURL string var notifyURL string
switch data.PayWay { switch data.PayWay {
case "hupi": case "hupi":
payWay = PayWayXunHu.Value
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
break break
case "payjs": case "geek":
payWay = PayWayJs.Value notifyURL = h.App.Config.GeekPayConfig.NotifyURL
notifyURL = h.App.Config.JPayConfig.NotifyURL
break break
case "alipay": case "alipay": // 支付宝商户支付
payWay = PayWayAlipay.Value
notifyURL = h.App.Config.AlipayConfig.NotifyURL notifyURL = h.App.Config.AlipayConfig.NotifyURL
break break
default: case "wechat": // 微信商户支付
payWay = PayWayWechat.Value
notifyURL = h.App.Config.WechatPayConfig.NotifyURL 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, Subject: product.Name,
Amount: amount, Amount: amount,
Status: types.OrderNotPaid, Status: types.OrderNotPaid,
PayWay: payWay, PayWay: data.PayWay,
PayType: data.PayType,
Remark: utils.JsonEncode(remark), Remark: utils.JsonEncode(remark),
} }
res = h.DB.Create(&order) res = h.DB.Create(&order)
@ -224,36 +240,26 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
return 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 var logo string
if data.PayWay == "alipay" { switch data.PayType {
case "alipay":
logo = "res/img/alipay.jpg" logo = "res/img/alipay.jpg"
} else if data.PayWay == "hupi" { break
if h.App.Config.HuPiPayConfig.Name == "wechat" { case "wechat":
logo = "res/img/wechat-pay.jpg" logo = "res/img/wechat-pay.jpg"
} else { break
logo = "res/img/alipay.jpg" case "qq":
} logo = "res/img/qq-pay.jpg"
} else if data.PayWay == "wechat" { break
logo = "res/img/wechat-pay.jpg" 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) file, err := h.fs.Open(logo)
if err != nil { if err != nil {
resp.ERROR(c, "error with open qrcode log file: "+err.Error()) 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() timestamp := time.Now().Unix()
signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey) signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey)
sign := utils.Sha256(signStr) sign := utils.Sha256(signStr)
var imageURL string 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)
if data.PayWay == "wechat" { imgData, err := utils.GenQrcode(payUrl, 400, file)
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)
if err != nil { if err != nil {
resp.ERROR(c, err.Error()) resp.ERROR(c, err.Error())
return return
} }
imgDataBase64 := base64.StdEncoding.EncodeToString(imgData) 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 移动端支付 // Mobile 移动端支付
func (h *PaymentHandler) Mobile(c *gin.Context) { func (h *PaymentHandler) Mobile(c *gin.Context) {
var data struct { 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"` ProductId uint `json:"product_id"`
} }
if err := c.ShouldBindJSON(&data); err != nil { 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() amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
var payWay string
var notifyURL, returnURL string var notifyURL, returnURL string
var payURL string var payURL string
switch data.PayWay { switch data.PayWay {
case "hupi": case "hupi":
payWay = PayWayXunHu.Name
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
returnURL = h.App.Config.HuPiPayConfig.ReturnURL returnURL = h.App.Config.HuPiPayConfig.ReturnURL
parse, _ := url.Parse(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 return
} }
payURL = r.URL payURL = r.URL
case "payjs": case "geek":
payWay = PayWayJs.Name //totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart()
notifyURL = h.App.Config.JPayConfig.NotifyURL //params := url.Values{}
returnURL = h.App.Config.JPayConfig.ReturnURL //params.Add("total_fee", fmt.Sprintf("%d", totalFee))
totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart() //params.Add("out_trade_no", orderNo)
params := url.Values{} //params.Add("body", product.Name)
params.Add("total_fee", fmt.Sprintf("%d", totalFee)) //params.Add("notify_url", notifyURL)
params.Add("out_trade_no", orderNo) //params.Add("auto", "0")
params.Add("body", product.Name) //payURL = h.geekPayService.Pay(params)
params.Add("notify_url", notifyURL)
params.Add("auto", "0")
payURL = h.jsPayService.PayH5(params)
case "alipay": case "alipay":
payWay = PayWayAlipay.Name
payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name) payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name)
if err != nil { if err != nil {
errMsg := "error with generating Alipay URL: " + err.Error() errMsg := "error with generating Alipay URL: " + err.Error()
@ -370,7 +360,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
return return
} }
case "wechat": case "wechat":
payWay = PayWayWechat.Name
payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP()) payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP())
if err != nil { if err != nil {
errMsg := "error with generating Wechat URL: " + err.Error() errMsg := "error with generating Wechat URL: " + err.Error()
@ -399,7 +388,8 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
Subject: product.Name, Subject: product.Name,
Amount: amount, Amount: amount,
Status: types.OrderNotPaid, Status: types.OrderNotPaid,
PayWay: payWay, PayWay: data.PayWay,
PayType: data.PayType,
Remark: utils.JsonEncode(remark), Remark: utils.JsonEncode(remark),
} }
res = h.DB.Create(&order) res = h.DB.Create(&order)
@ -506,20 +496,25 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
// GetPayWays 获取支付方式 // GetPayWays 获取支付方式
func (h *PaymentHandler) GetPayWays(c *gin.Context) { func (h *PaymentHandler) GetPayWays(c *gin.Context) {
data := gin.H{} payWays := make([]gin.H, 0)
if h.App.Config.AlipayConfig.Enabled { 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 { 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 { if h.App.Config.GeekPayConfig.Enabled {
data["payjs"] = gin.H{"name": h.App.Config.JPayConfig.Name} 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 { 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 虎皮椒支付异步回调 // HuPiPayNotify 虎皮椒支付异步回调
@ -593,12 +588,12 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
// 校验订单支付状态 // 校验订单支付状态
tradeNo := c.Request.Form.Get("payjs_order_id") tradeNo := c.Request.Form.Get("payjs_order_id")
err = h.jsPayService.TradeVerify(tradeNo) //err = h.geekPayService.TradeVerify(tradeNo)
if err != nil { //if err != nil {
logger.Error("订单校验失败:", err) // logger.Error("订单校验失败:", err)
c.String(http.StatusOK, "fail") // c.String(http.StatusOK, "fail")
return // return
} //}
err = h.notify(orderNo, tradeNo) err = h.notify(orderNo, tradeNo)
if err != nil { if err != nil {

View File

@ -11,10 +11,10 @@ import (
type TestHandler struct { type TestHandler struct {
db *gorm.DB db *gorm.DB
snowflake *service.Snowflake 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} return &TestHandler{db: db, snowflake: snowflake, js: js}
} }

BIN
api/res/img/geek-pay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
api/res/img/qq-pay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -18,6 +18,7 @@ type Order struct {
Status types.OrderStatus Status types.OrderStatus
Remark string Remark string
PayTime int64 PayTime int64
PayWay string // 支付方式 PayWay string // 支付渠道
PayType string // 支付类型
DeletedAt gorm.DeletedAt DeletedAt gorm.DeletedAt
} }

View File

@ -16,5 +16,6 @@ type Order struct {
Status types.OrderStatus `json:"status"` Status types.OrderStatus `json:"status"`
PayTime int64 `json:"pay_time"` PayTime int64 `json:"pay_time"`
PayWay string `json:"pay_way"` PayWay string `json:"pay_way"`
PayType string `json:"pay_type"`
Remark types.OrderRemark `json:"remark"` Remark types.OrderRemark `json:"remark"`
} }

View File

@ -12,3 +12,4 @@ 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_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`; 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`;

View File

@ -110,6 +110,7 @@
overflow hidden overflow hidden
cursor pointer cursor pointer
transition: all 0.3s ease; /* */ transition: all 0.3s ease; /* */
margin-bottom 20px
.image-container { .image-container {
display flex display flex
@ -175,10 +176,11 @@
.pay-way { .pay-way {
padding 10px 0 padding 10px 0
display flex display flex
justify-content: space-between justify-content: center
flex-wrap wrap
.iconfont { .el-button {
margin-right 5px margin 10px 5px 0 5px
} }
} }
} }

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4125778 */ font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1725929120246') format('woff2'), src: url('iconfont.woff2?t=1726612860394') format('woff2'),
url('iconfont.woff?t=1725929120246') format('woff'), url('iconfont.woff?t=1726612860394') format('woff'),
url('iconfont.ttf?t=1725929120246') format('truetype'); url('iconfont.ttf?t=1726612860394') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale; -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 { .icon-luma:before {
content: "\e704"; content: "\e704";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,34 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "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", "icon_id": "41645421",
"name": "luma-logo", "name": "luma-logo",

Binary file not shown.

View File

@ -36,57 +36,63 @@
</el-alert> </el-alert>
</div> </div>
<ItemList :items="list" v-if="list.length > 0" :gap="15" :width="240"> <el-row v-if="list.length > 0" :gutter="20" class="list-box">
<template #default="scope"> <el-col v-for="item in list" :key="item" :span="6">
<div class="product-item"> <div class="product-item">
<div class="image-container"> <div class="image-container">
<el-image :src="vipImg" fit="cover"/> <el-image :src="vipImg" fit="cover"/>
</div> </div>
<div class="product-title"> <div class="product-title">
<span class="name">{{ scope.item.name }}</span> <span class="name">{{ item.name }}</span>
</div> </div>
<div class="product-info"> <div class="product-info">
<div class="info-line"> <div class="info-line">
<span class="label">商品原价</span> <span class="label">商品原价</span>
<span class="price">{{ scope.item.price }}</span> <span class="price">{{ item.price }}</span>
</div> </div>
<div class="info-line"> <div class="info-line">
<span class="label">促销立减</span> <span class="label">促销立减</span>
<span class="price">{{ scope.item.discount }}</span> <span class="price">{{ item.discount }}</span>
</div> </div>
<div class="info-line"> <div class="info-line">
<span class="label">有效期</span> <span class="label">有效期</span>
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}</span> <span class="expire" v-if="item.days > 0">{{ item.days }}</span>
<span class="expire" v-else>长期有效</span> <span class="expire" v-else>长期有效</span>
</div> </div>
<div class="info-line"> <div class="info-line">
<span class="label">算力值</span> <span class="label">算力值</span>
<span class="power" v-if="scope.item.power > 0">{{ scope.item.power }}</span> <span class="power">{{ item.power }}</span>
<span class="power" v-else>{{ vipMonthPower }}</span>
</div> </div>
<div class="pay-way"> <div class="pay-way">
<el-button type="primary" @click="alipay(scope.item)" size="small" v-if="payWays['alipay']">
<i class="iconfont icon-alipay"></i> 支付宝
</el-button>
<el-button type="success" @click="huPiPay(scope.item)" size="small" v-if="payWays['hupi']">
<span v-if="payWays['hupi']['name'] === 'wechat'"><i
class="iconfont icon-wechat-pay"></i> 微信</span>
<span v-else><i class="iconfont icon-alipay"></i> 支付宝</span>
</el-button>
<el-button type="success" @click="PayJs(scope.item)" size="small" v-if="payWays['payjs']"> <span type="primary" v-for="payWay in payWays" @click="genPayQrcode(item,payWay)" :key="payWay">
<span><i class="iconfont icon-wechat-pay"></i> 微信</span> <el-button v-if="payWay.pay_type==='alipay'" color="#409eff" circle>
<i class="iconfont icon-alipay" ></i>
</el-button> </el-button>
<el-button type="success" @click="wechatPay(scope.item)" size="small" v-if="payWays['wechat']"> <el-button v-else-if="payWay.pay_type==='qq'" class="qq" circle>
<i class="iconfont icon-wechat-pay"></i> 微信 <i class="iconfont icon-qq" ></i>
</el-button> </el-button>
<el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" circle>
<i class="iconfont icon-paypal"></i>
</el-button>
<el-button v-else-if="payWay.pay_type==='jd'" class="jd" circle>
<i class="iconfont icon-jd-pay"></i>
</el-button>
<el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
<i class="iconfont icon-douyin"></i>
</el-button>
<el-button v-else circle class="wechat">
<i class="iconfont icon-wechat-pay"></i>
</el-button>
</span>
</div> </div>
</div> </div>
</div> </div>
</template> </el-col>
</ItemList> </el-row>
<el-empty description="暂无数据" />
<h2 class="headline">消费账单</h2> <h2 class="headline">消费账单</h2>
@ -132,7 +138,7 @@
<el-icon> <el-icon>
<InfoFilled/> <InfoFilled/>
</el-icon> </el-icon>
<span class="text">请打开手机{{ payName }}扫码支付</span> <span class="text">请打开手机扫码支付</span>
</div> </div>
</div> </div>
@ -146,7 +152,6 @@
import {onMounted, ref} from "vue" import {onMounted, ref} from "vue"
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import ItemList from "@/components/ItemList.vue";
import {InfoFilled, SuccessFilled} from "@element-plus/icons-vue"; import {InfoFilled, SuccessFilled} from "@element-plus/icons-vue";
import {checkSession, getSystemInfo} from "@/store/cache"; import {checkSession, getSystemInfo} from "@/store/cache";
import UserProfile from "@/components/UserProfile.vue"; import UserProfile from "@/components/UserProfile.vue";
@ -176,19 +181,16 @@ const text = ref("")
const user = ref(null) const user = ref(null)
const isLogin = ref(false) const isLogin = ref(false)
const router = useRouter() const router = useRouter()
const curPayProduct = ref(null)
const activeOrderNo = ref("") const activeOrderNo = ref("")
const countDownRef = ref(null) const countDownRef = ref(null)
const orderTimeout = ref(1800) const orderTimeout = ref(1800)
const loading = ref(true) const loading = ref(true)
const orderPayInfoText = ref("") const orderPayInfoText = ref("")
const vipMonthPower = ref(0)
const powerPrice = ref(0)
const payWays = ref({}) const payWays = ref([])
const amount = ref(0) const amount = ref(0)
const payName = ref("支付宝") const curPayProduct = ref(null)
const curPay = ref("alipay") // const curPayWay = ref({pay_way: "", pay_type:""})
const vipInfoText = ref("") const vipInfoText = ref("")
const store = useSharedStore() const store = useSharedStore()
const profileKey = ref(0) const profileKey = ref(0)
@ -215,8 +217,6 @@ onMounted(() => {
if (res.data['order_pay_timeout'] > 0) { if (res.data['order_pay_timeout'] > 0) {
orderTimeout.value = res.data['order_pay_timeout'] orderTimeout.value = res.data['order_pay_timeout']
} }
vipMonthPower.value = res.data['vip_month_power']
powerPrice.value = res.data['power_price']
vipInfoText.value = res.data['vip_info_text'] vipInfoText.value = res.data['vip_info_text']
}).catch(e => { }).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message) ElMessage.error("获取系统配置失败:" + e.message)
@ -231,22 +231,24 @@ onMounted(() => {
// refresh payment qrcode // refresh payment qrcode
const refreshPayCode = () => { const refreshPayCode = () => {
if (curPay.value === 'alipay') { genPayQrcode(curPayProduct.value, curPayWay.value)
alipay(curPayProduct.value)
} else if (curPay.value === 'hupi') {
huPiPay(curPayProduct.value)
} else if (curPay.value === 'payjs') {
PayJs(curPayProduct.value)
}
} }
const genPayQrcode = () => { const genPayQrcode = (product, payWay) => {
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
loading.value = true loading.value = true
text.value = "" text.value = ""
curPayProduct.value = product
curPayWay.value = payWay
amount.value = (product.price - product.discount).toFixed(2)
httpPost("/api/payment/qrcode", { httpPost("/api/payment/qrcode", {
pay_way: curPay.value, pay_way: payWay.pay_way,
pay_type: payWay.pay_type,
product_id: curPayProduct.value.id, product_id: curPayProduct.value.id,
user_id: user.value.id
}).then(res => { }).then(res => {
showPayDialog.value = true showPayDialog.value = true
qrcode.value = res.data['image'] qrcode.value = res.data['image']
@ -262,67 +264,6 @@ const genPayQrcode = () => {
}) })
} }
const alipay = (row) => {
payName.value = "支付宝"
curPay.value = "alipay"
amount.value = (row.price - row.discount).toFixed(2)
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
if (row) {
curPayProduct.value = row
}
genPayQrcode()
}
//
const huPiPay = (row) => {
payName.value = payWays.value["hupi"]["name"] === "wechat" ? '微信' : '支付宝'
curPay.value = "hupi"
amount.value = (row.price - row.discount).toFixed(2)
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
if (row) {
curPayProduct.value = row
}
genPayQrcode()
}
// PayJS
const PayJs = (row) => {
payName.value = '微信'
curPay.value = "payjs"
amount.value = (row.price - row.discount).toFixed(2)
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
if (row) {
curPayProduct.value = row
}
genPayQrcode()
}
const wechatPay = (row) => {
payName.value = '微信'
curPay.value = "wechat"
amount.value = (row.price - row.discount).toFixed(2)
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
if (row) {
curPayProduct.value = row
}
genPayQrcode()
}
const queryOrder = (orderNo) => { const queryOrder = (orderNo) => {
httpGet("/api/order/query", {order_no: orderNo}).then(res => { httpGet("/api/order/query", {order_no: orderNo}).then(res => {
@ -331,11 +272,7 @@ const queryOrder = (orderNo) => {
queryOrder(orderNo) queryOrder(orderNo)
} else if (res.data.status === 2) { } else if (res.data.status === 2) {
text.value = "支付成功,正在刷新页面" text.value = "支付成功,正在刷新页面"
if (curPay.value === "payjs") {
setTimeout(() => location.reload(), 3000)
} else {
setTimeout(() => location.reload(), 500) setTimeout(() => location.reload(), 500)
}
} else { } else {
// //
if (activeOrderNo.value === orderNo) { if (activeOrderNo.value === orderNo) {