Geek Pay notify is ready

This commit is contained in:
RockYang 2024-09-18 18:07:49 +08:00
parent 158db83965
commit d8cb92d8d4
16 changed files with 270 additions and 544 deletions

View File

@ -219,10 +219,6 @@ func needLogin(c *gin.Context) bool {
c.Request.URL.Path == "/api/product/list" || c.Request.URL.Path == "/api/product/list" ||
c.Request.URL.Path == "/api/menu/list" || c.Request.URL.Path == "/api/menu/list" ||
c.Request.URL.Path == "/api/markMap/client" || c.Request.URL.Path == "/api/markMap/client" ||
c.Request.URL.Path == "/api/payment/alipay/notify" ||
c.Request.URL.Path == "/api/payment/hupipay/notify" ||
c.Request.URL.Path == "/api/payment/payjs/notify" ||
c.Request.URL.Path == "/api/payment/wechat/notify" ||
c.Request.URL.Path == "/api/payment/doPay" || c.Request.URL.Path == "/api/payment/doPay" ||
c.Request.URL.Path == "/api/payment/payWays" || c.Request.URL.Path == "/api/payment/payWays" ||
c.Request.URL.Path == "/api/suno/client" || c.Request.URL.Path == "/api/suno/client" ||
@ -231,6 +227,7 @@ func needLogin(c *gin.Context) bool {
c.Request.URL.Path == "/api/download" || c.Request.URL.Path == "/api/download" ||
c.Request.URL.Path == "/api/video/client" || c.Request.URL.Path == "/api/video/client" ||
strings.HasPrefix(c.Request.URL.Path, "/api/test") || strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
strings.HasPrefix(c.Request.URL.Path, "/api/payment/notify/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") || strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
strings.HasPrefix(c.Request.URL.Path, "/api/config/") || strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") || strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||

View File

@ -57,8 +57,7 @@ type AlipayConfig struct {
PublicKey string // 用户公钥文件路径 PublicKey string // 用户公钥文件路径
AlipayPublicKey string // 支付宝公钥文件路径 AlipayPublicKey string // 支付宝公钥文件路径
RootCert string // Root 秘钥路径 RootCert string // Root 秘钥路径
NotifyURL string // 异步通知回调 NotifyHost string // 通知回调地址
ReturnURL string // 支付成功返回地址
} }
type WechatPayConfig struct { type WechatPayConfig struct {
@ -68,18 +67,16 @@ type WechatPayConfig struct {
SerialNo string // 商户证书的证书序列号 SerialNo string // 商户证书的证书序列号
PrivateKey string // 用户私钥文件路径 PrivateKey string // 用户私钥文件路径
ApiV3Key string // API V3 秘钥 ApiV3Key string // API V3 秘钥
NotifyURL string // 异步通知回调 NotifyHost string // 通知回调地址
ReturnURL string // 支付成功返回地址
} }
type HuPiPayConfig struct { //虎皮椒第四方支付配置 type HuPiPayConfig struct { //虎皮椒第四方支付配置
Enabled bool // 是否启用该支付通道 Enabled bool // 是否启用该支付通道
Name string // 支付名称wechat/alipay Name string // 支付名称wechat/alipay
AppId string // App ID AppId string // App ID
AppSecret string // app 密钥 AppSecret string // app 密钥
ApiURL string // 支付网关 ApiURL string // 支付网关
NotifyURL string // 异步通知回调 NotifyHost string // 通知回调地址
ReturnURL string // 支付成功返回地址
} }
// GeekPayConfig GEEK支付配置 // GeekPayConfig GEEK支付配置
@ -88,8 +85,7 @@ type GeekPayConfig struct {
AppId string // 商户 ID AppId string // 商户 ID
PrivateKey string // 私钥 PrivateKey string // 私钥
ApiURL string // API 网关 ApiURL string // API 网关
NotifyURL string // 异步回调地址 NotifyHost string // 通知回调地址
ReturnURL string // 支付成功返回地址
} }
type XXLConfig struct { // XXL 任务调度配置 type XXLConfig struct { // XXL 任务调度配置

View File

@ -9,7 +9,6 @@ package handler
import ( import (
"embed" "embed"
"encoding/base64"
"fmt" "fmt"
"geekai/core" "geekai/core"
"geekai/core/types" "geekai/core/types"
@ -20,7 +19,6 @@ import (
"geekai/utils/resp" "geekai/utils/resp"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"net/http" "net/http"
"net/url"
"sync" "sync"
"time" "time"
@ -71,62 +69,94 @@ func NewPaymentHandler(
} }
} }
func (h *PaymentHandler) DoPay(c *gin.Context) { func (h *PaymentHandler) Pay(c *gin.Context) {
orderNo := h.GetTrim(c, "order_no") payWay := c.Query("pay_way")
t := h.GetInt(c, "t", 0) payType := c.Query("pay_type")
sign := h.GetTrim(c, "sign") productId := c.Query("pid")
signStr := fmt.Sprintf("%s-%d-%s", orderNo, t, h.signKey) device := c.Query("device")
newSign := utils.Sha256(signStr) userId := c.Query("user_id")
if newSign != sign {
resp.ERROR(c, "订单签名错误!") var product model.Product
err := h.DB.First(&product, productId).Error
if err != nil {
resp.ERROR(c, "Product not found")
return return
} }
// 检查二维码是否过期 orderNo, err := h.snowflake.Next(false)
if time.Now().Unix()-int64(t) > int64(h.App.SysConfig.OrderPayTimeout) { if err != nil {
resp.ERROR(c, "支付二维码已过期,请重新生成!") resp.ERROR(c, "error with generate trade no: "+err.Error())
return
}
var user model.User
err = h.DB.Where("id", userId).First(&user).Error
if err != nil {
resp.NotAuth(c)
return
}
// 创建订单
remark := types.OrderRemark{
Days: product.Days,
Power: product.Power,
Name: product.Name,
Price: product.Price,
Discount: product.Discount,
}
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
order := model.Order{
UserId: user.Id,
Username: user.Username,
ProductId: product.Id,
OrderNo: orderNo,
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
PayWay: payWay,
PayType: payType,
Remark: utils.JsonEncode(remark),
}
err = h.DB.Create(&order).Error
if err != nil {
resp.ERROR(c, "error with create order: "+err.Error())
return return
} }
if orderNo == "" { var payURL string
resp.ERROR(c, types.InvalidArgs) if payWay == "alipay" { // 支付宝
return money := fmt.Sprintf("%.2f", order.Amount)
} notifyURL := fmt.Sprintf("%s/api/payment/notify/alipay", h.App.Config.AlipayConfig.NotifyHost)
returnURL := fmt.Sprintf("%s/member", h.App.Config.AlipayConfig.NotifyHost)
var order model.Order if device == "mobile" {
res := h.DB.Where("order_no = ?", orderNo).First(&order) payURL, err = h.alipayService.PayMobile(payment.AlipayParams{
if res.Error != nil { OutTradeNo: orderNo,
resp.ERROR(c, "Order not found") Subject: product.Name,
return TotalFee: money,
} ReturnURL: returnURL,
NotifyURL: notifyURL,
// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回 })
if order.Status == types.OrderPaidSuccess { } else {
resp.ERROR(c, "订单已支付成功,无需重复支付!") payURL, err = h.alipayService.PayPC(payment.AlipayParams{
return OutTradeNo: orderNo,
} Subject: product.Name,
TotalFee: money,
// 更新扫码状态 ReturnURL: returnURL,
h.DB.Model(&order).UpdateColumn("status", types.OrderScanned) NotifyURL: notifyURL,
})
if order.PayWay == "alipay" { // 支付宝 }
amount := fmt.Sprintf("%.2f", order.Amount)
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject)
if err != nil { if err != nil {
resp.ERROR(c, "error with generate pay url: "+err.Error()) resp.ERROR(c, "error with generate pay url: "+err.Error())
return return
} }
c.Redirect(302, uri)
return
} else if order.PayWay == "hupi" { // 虎皮椒支付 } else if order.PayWay == "hupi" { // 虎皮椒支付
params := payment.HuPiPayReq{ params := payment.HuPiPayReq{
Version: "1.1", Version: "1.1",
TradeOrderId: orderNo, TradeOrderId: orderNo,
TotalFee: fmt.Sprintf("%f", order.Amount), TotalFee: fmt.Sprintf("%f", order.Amount),
Title: order.Subject, Title: order.Subject,
NotifyURL: h.App.Config.HuPiPayConfig.NotifyURL, NotifyURL: fmt.Sprintf("%s/api/payment/notify/hupi", h.App.Config.HuPiPayConfig.NotifyHost),
WapName: "极客学长", WapName: "GeekAI助手",
} }
r, err := h.huPiPayService.Pay(params) r, err := h.huPiPayService.Pay(params)
if err != nil { if err != nil {
@ -136,7 +166,8 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
c.Redirect(302, r.URL) c.Redirect(302, r.URL)
} else if order.PayWay == "wechat" { } else if order.PayWay == "wechat" {
uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject) //uri, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(order.Amount*100), order.Subject)
uri, err := h.wechatPayService.PayUrlNative(payment.WechatPayParams{})
if err != nil { if err != nil {
resp.ERROR(c, err.Error()) resp.ERROR(c, err.Error())
return return
@ -144,261 +175,28 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
c.Redirect(302, uri) c.Redirect(302, uri)
} else if order.PayWay == "geek" { } else if order.PayWay == "geek" {
notifyURL := fmt.Sprintf("%s/api/payment/notify/geek", h.App.Config.GeekPayConfig.NotifyHost)
returnURL := fmt.Sprintf("%s/member", h.App.Config.GeekPayConfig.NotifyHost)
params := payment.GeekPayParams{ params := payment.GeekPayParams{
OutTradeNo: orderNo, OutTradeNo: orderNo,
Method: "web", Method: "web",
Name: order.Subject, Name: order.Subject,
Money: fmt.Sprintf("%f", order.Amount), Money: fmt.Sprintf("%f", order.Amount),
ClientIP: c.ClientIP(), ClientIP: c.ClientIP(),
Device: "pc", Device: device,
Type: "alipay", Type: payType,
ReturnURL: returnURL,
NotifyURL: notifyURL,
} }
s, err := h.geekPayService.Pay(params) res, err := h.geekPayService.Pay(params)
if err != nil { if err != nil {
resp.ERROR(c, err.Error()) resp.ERROR(c, err.Error())
return return
} }
resp.SUCCESS(c, s) payURL = res.PayURL
} }
//resp.ERROR(c, "Invalid operations") resp.SUCCESS(c, payURL)
}
// PayQrcode 生成支付 URL 二维码
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
var data struct {
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)
return
}
var product model.Product
res := h.DB.First(&product, data.ProductId)
if res.Error != nil {
resp.ERROR(c, "Product not found")
return
}
orderNo, err := h.snowflake.Next(false)
if err != nil {
resp.ERROR(c, "error with generate trade no: "+err.Error())
return
}
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
return
}
var notifyURL string
switch data.PayWay {
case "hupi":
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
break
case "geek":
notifyURL = h.App.Config.GeekPayConfig.NotifyURL
break
case "alipay": // 支付宝商户支付
notifyURL = h.App.Config.AlipayConfig.NotifyURL
break
case "wechat": // 微信商户支付
notifyURL = h.App.Config.WechatPayConfig.NotifyURL
default:
resp.ERROR(c, "Invalid pay way")
return
}
// 创建订单
remark := types.OrderRemark{
Days: product.Days,
Power: product.Power,
Name: product.Name,
Price: product.Price,
Discount: product.Discount,
}
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
order := model.Order{
UserId: user.Id,
Username: user.Username,
ProductId: product.Id,
OrderNo: orderNo,
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
PayWay: data.PayWay,
PayType: data.PayType,
Remark: utils.JsonEncode(remark),
}
res = h.DB.Create(&order)
if res.Error != nil || res.RowsAffected == 0 {
resp.ERROR(c, "error with create order: "+res.Error.Error())
return
}
var logo string
switch data.PayType {
case "alipay":
logo = "res/img/alipay.jpg"
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())
return
}
parse, err := url.Parse(notifyURL)
if err != nil {
resp.ERROR(c, err.Error())
return
}
timestamp := time.Now().Unix()
signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey)
sign := utils.Sha256(signStr)
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": payUrl})
}
// Mobile 移动端支付
func (h *PaymentHandler) Mobile(c *gin.Context) {
var data struct {
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 {
resp.ERROR(c, types.InvalidArgs)
return
}
var product model.Product
res := h.DB.First(&product, data.ProductId)
if res.Error != nil {
resp.ERROR(c, "Product not found")
return
}
orderNo, err := h.snowflake.Next(false)
if err != nil {
resp.ERROR(c, "error with generate trade no: "+err.Error())
return
}
user, err := h.GetLoginUser(c)
if err != nil {
resp.NotAuth(c)
return
}
amount, _ := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Float64()
var notifyURL, returnURL string
var payURL string
switch data.PayWay {
case "hupi":
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL)
baseURL := fmt.Sprintf("%s://%s", parse.Scheme, parse.Host)
params := payment.HuPiPayReq{
Version: "1.1",
TradeOrderId: orderNo,
TotalFee: fmt.Sprintf("%f", amount),
Title: product.Name,
NotifyURL: notifyURL,
ReturnURL: returnURL,
CallbackURL: returnURL,
WapName: "极客学长",
WapUrl: baseURL,
Type: "WAP",
}
r, err := h.huPiPayService.Pay(params)
if err != nil {
errMsg := "error with generating Pay Hupi URL: " + err.Error()
logger.Error(errMsg)
resp.ERROR(c, errMsg)
return
}
payURL = r.URL
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":
payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name)
if err != nil {
errMsg := "error with generating Alipay URL: " + err.Error()
resp.ERROR(c, errMsg)
return
}
case "wechat":
payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP())
if err != nil {
errMsg := "error with generating Wechat URL: " + err.Error()
logger.Error(errMsg)
resp.ERROR(c, errMsg)
return
}
default:
resp.ERROR(c, "Unsupported pay way: "+data.PayWay)
return
}
// 创建订单
remark := types.OrderRemark{
Days: product.Days,
Power: product.Power,
Name: product.Name,
Price: product.Price,
Discount: product.Discount,
}
order := model.Order{
UserId: user.Id,
Username: user.Username,
ProductId: product.Id,
OrderNo: orderNo,
Subject: product.Name,
Amount: amount,
Status: types.OrderNotPaid,
PayWay: data.PayWay,
PayType: data.PayType,
Remark: utils.JsonEncode(remark),
}
res = h.DB.Create(&order)
if res.Error != nil || res.RowsAffected == 0 {
resp.ERROR(c, "error with create order: "+res.Error.Error())
return
}
resp.SUCCESS(c, gin.H{"url": payURL, "order_no": orderNo})
} }
// 异步通知回调公共逻辑 // 异步通知回调公共逻辑
@ -505,14 +303,14 @@ func (h *PaymentHandler) GetPayWays(c *gin.Context) {
} }
if h.App.Config.GeekPayConfig.Enabled { 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": "alipay"})
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wechat"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "wxpay"})
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qq"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "qqpay"})
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jd"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "jdpay"})
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "douyin"})
payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"}) payWays = append(payWays, gin.H{"pay_way": "geek", "pay_type": "paypal"})
} }
if h.App.Config.WechatPayConfig.Enabled { if h.App.Config.WechatPayConfig.Enabled {
payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wechat"}) payWays = append(payWays, gin.H{"pay_way": "wechat", "pay_type": "wxpay"})
} }
resp.SUCCESS(c, payWays) resp.SUCCESS(c, payWays)
} }
@ -570,32 +368,28 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
c.String(http.StatusOK, "success") c.String(http.StatusOK, "success")
} }
// PayJsNotify PayJs 支付异步回调 // GeekPayNotify 支付异步回调
func (h *PaymentHandler) PayJsNotify(c *gin.Context) { func (h *PaymentHandler) GeekPayNotify(c *gin.Context) {
err := c.Request.ParseForm() var params = make(map[string]string)
if err != nil { for k := range c.Request.URL.Query() {
params[k] = c.Query(k)
}
logger.Infof("收到GeekPay订单支付回调%+v", params)
// 检查支付状态
if params["trade_status"] != "TRADE_SUCCESS" {
c.String(http.StatusOK, "success")
return
}
sign := h.geekPayService.Sign(params)
if sign != c.Query("sign") {
logger.Errorf("签名验证失败, %s, %s", sign, c.Query("sign"))
c.String(http.StatusOK, "fail") c.String(http.StatusOK, "fail")
return return
} }
orderNo := c.Request.Form.Get("out_trade_no") err := h.notify(params["out_trade_no"], params["trade_no"])
returnCode := c.Request.Form.Get("return_code")
logger.Infof("收到PayJs订单支付回调订单 NO%s支付结果代码%v", orderNo, returnCode)
// 支付失败
if returnCode != "1" {
return
}
// 校验订单支付状态
tradeNo := c.Request.Form.Get("payjs_order_id")
//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 { if err != nil {
c.String(http.StatusOK, "fail") c.String(http.StatusOK, "fail")
return return

View File

@ -372,14 +372,11 @@ func main() {
}), }),
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) { fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
group := s.Engine.Group("/api/payment/") group := s.Engine.Group("/api/payment/")
group.GET("doPay", h.DoPay) group.GET("doPay", h.Pay)
group.GET("payWays", h.GetPayWays) group.GET("payWays", h.GetPayWays)
group.POST("qrcode", h.PayQrcode) group.POST("notify/alipay", h.AlipayNotify)
group.POST("mobile", h.Mobile) group.GET("notify/geek", h.GeekPayNotify)
group.POST("alipay/notify", h.AlipayNotify) group.POST("notify/wechat", h.WechatPayNotify)
group.POST("hupipay/notify", h.HuPiPayNotify)
group.POST("payjs/notify", h.PayJsNotify)
group.POST("wechat/notify", h.WechatPayNotify)
}), }),
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) { fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
group := s.Engine.Group("/api/admin/product/") group := s.Engine.Group("/api/admin/product/")

View File

@ -44,9 +44,7 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
//client.DebugSwitch = gopay.DebugOn // 开启调试模式 //client.DebugSwitch = gopay.DebugOn // 开启调试模式
client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间 client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8 SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8
SetSignType(alipay.RSA2). // 设置签名类型,不设置默认 RSA2 SetSignType(alipay.RSA2) // 设置签名类型,不设置默认 RSA2
SetReturnUrl(config.ReturnURL). // 设置返回URL
SetNotifyUrl(config.NotifyURL)
if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil { if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil {
return nil, fmt.Errorf("error with load payment public key: %v", err) return nil, fmt.Errorf("error with load payment public key: %v", err)
@ -55,23 +53,33 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
return &AlipayService{config: &config, client: client}, nil return &AlipayService{config: &config, client: client}, nil
} }
func (s *AlipayService) PayUrlMobile(outTradeNo string, amount string, subject string) (string, error) { type AlipayParams struct {
OutTradeNo string `json:"out_trade_no"`
Subject string `json:"subject"`
TotalFee string `json:"total_fee"`
ReturnURL string `json:"return_url"`
NotifyURL string `json:"notify_url"`
}
func (s *AlipayService) PayMobile(params AlipayParams) (string, error) {
bm := make(gopay.BodyMap) bm := make(gopay.BodyMap)
bm.Set("subject", subject) bm.Set("subject", params.Subject)
bm.Set("out_trade_no", outTradeNo) bm.Set("out_trade_no", params.OutTradeNo)
bm.Set("quit_url", s.config.ReturnURL) bm.Set("quit_url", params.ReturnURL)
bm.Set("total_amount", amount) bm.Set("total_amount", params.TotalFee)
bm.Set("return_url", params.ReturnURL)
bm.Set("notify_url", params.NotifyURL)
bm.Set("product_code", "QUICK_WAP_WAY") bm.Set("product_code", "QUICK_WAP_WAY")
return s.client.TradeWapPay(context.Background(), bm) return s.client.TradeWapPay(context.Background(), bm)
} }
func (s *AlipayService) PayUrlPc(outTradeNo string, amount string, subject string) (string, error) { func (s *AlipayService) PayPC(params AlipayParams) (string, error) {
bm := make(gopay.BodyMap) bm := make(gopay.BodyMap)
bm.Set("subject", subject) bm.Set("subject", params.Subject)
bm.Set("out_trade_no", outTradeNo) bm.Set("out_trade_no", params.OutTradeNo)
bm.Set("total_amount", amount) bm.Set("total_amount", params.TotalFee)
bm.Set("product_code", "FAST_INSTANT_TRADE_PAY") bm.Set("product_code", "FAST_INSTANT_TRADE_PAY")
return s.client.TradePagePay(context.Background(), bm) return s.client.SetNotifyUrl(params.NotifyURL).SetReturnUrl(params.ReturnURL).TradePagePay(context.Background(), bm)
} }
// TradeVerify 交易验证 // TradeVerify 交易验证

View File

@ -8,15 +8,11 @@ package payment
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import ( import (
"crypto" "encoding/json"
"crypto/rand" "errors"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt" "fmt"
"geekai/core/types" "geekai/core/types"
"geekai/utils"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -46,41 +42,37 @@ type GeekPayParams struct {
ClientIP string `json:"clientip"` //用户IP地址 ClientIP string `json:"clientip"` //用户IP地址
SubOpenId string `json:"sub_openid"` // 微信用户 openid仅小程序支付需要 SubOpenId string `json:"sub_openid"` // 微信用户 openid仅小程序支付需要
SubAppId string `json:"sub_appid"` // 小程序 AppId仅小程序支付需要 SubAppId string `json:"sub_appid"` // 小程序 AppId仅小程序支付需要
NotifyURL string `json:"notify_url"`
ReturnURL string `json:"return_url"`
} }
// Pay 支付订单 // Pay 支付订单
func (s *GeekPayService) Pay(params GeekPayParams) (string, error) { func (s *GeekPayService) Pay(params GeekPayParams) (*GeekPayResp, error) {
if params.Type == "wechat" {
params.Type = "wxpay"
}
p := map[string]string{ p := map[string]string{
"pid": s.config.AppId, "pid": s.config.AppId,
"method": params.Method, //"method": params.Method,
"device": params.Device, "device": params.Device,
"type": params.Type, "type": params.Type,
"out_trade_no": params.OutTradeNo, "out_trade_no": params.OutTradeNo,
"name": params.Name, "name": params.Name,
"money": params.Money, "money": params.Money,
"clientip": params.ClientIP, "clientip": params.ClientIP,
"sub_openid": params.SubOpenId, "notify_url": params.NotifyURL,
"sub_appid": params.SubAppId, "return_url": params.ReturnURL,
"notify_url": s.config.NotifyURL,
"return_url": s.config.ReturnURL,
"timestamp": fmt.Sprintf("%d", time.Now().Unix()), "timestamp": fmt.Sprintf("%d", time.Now().Unix()),
} }
sign, err := s.Sign(p) p["sign"] = s.Sign(p)
if err != nil { p["sign_type"] = "MD5"
return "", err
}
p["sign"] = sign
p["sign_type"] = "RSA"
return s.sendRequest(s.config.ApiURL, p) return s.sendRequest(s.config.ApiURL, p)
} }
func (s *GeekPayService) Sign(params map[string]string) (string, error) { func (s *GeekPayService) Sign(params map[string]string) string {
// 按字母顺序排序参数 // 按字母顺序排序参数
var keys []string var keys []string
for k := range params { for k := range params {
if params[k] == "" || k == "sign" || k == "sign_type" {
continue
}
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
@ -93,44 +85,48 @@ func (s *GeekPayService) Sign(params map[string]string) (string, error) {
signStr.WriteString(params[k]) signStr.WriteString(params[k])
signStr.WriteString("&") signStr.WriteString("&")
} }
signString := strings.TrimSuffix(signStr.String(), "&") signString := strings.TrimSuffix(signStr.String(), "&") + s.config.PrivateKey
// 使用RSA私钥签名 return utils.Md5(signString)
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) { type GeekPayResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
TradeNo string `json:"trade_no"`
PayURL string `json:"payurl"`
QrCode string `json:"qrcode"`
UrlScheme string `json:"urlscheme"` // 小程序跳转支付链接
}
func (s *GeekPayService) sendRequest(endpoint string, params map[string]string) (*GeekPayResp, error) {
form := url.Values{} form := url.Values{}
for k, v := range params { for k, v := range params {
form.Add(k, v) form.Add(k, v)
} }
resp, err := http.PostForm(apiEndpoint, form) apiURL := fmt.Sprintf("%s/mapi.php", endpoint)
logger.Infof(apiURL)
resp, err := http.PostForm(apiURL, form)
if err != nil { if err != nil {
return "", err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
logger.Debugf(string(body))
if err != nil { if err != nil {
return "", err return nil, err
} }
return string(body), nil var r GeekPayResp
err = json.Unmarshal(body, &r)
if err != nil {
return nil, errors.New("当前支付渠道暂不支持")
}
if r.Code != 1 {
return nil, errors.New(r.Msg)
}
return &r, nil
} }

View File

@ -46,18 +46,28 @@ func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) {
return &WechatPayService{config: &config, client: client}, nil return &WechatPayService{config: &config, client: client}, nil
} }
func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject string) (string, error) { type WechatPayParams struct {
OutTradeNo string `json:"out_trade_no"`
TotalFee int `json:"total_fee"`
Subject string `json:"subject"`
ClientIP string `json:"client_ip"`
ReturnURL string `json:"return_url"`
NotifyURL string `json:"notify_url"`
}
func (s *WechatPayService) PayUrlNative(params WechatPayParams) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339) expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap // 初始化 BodyMap
bm := make(gopay.BodyMap) bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId). bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId). Set("mchid", s.config.MchId).
Set("description", subject). Set("description", params.Subject).
Set("out_trade_no", outTradeNo). Set("out_trade_no", params.OutTradeNo).
Set("time_expire", expire). Set("time_expire", expire).
Set("notify_url", s.config.NotifyURL). Set("notify_url", params.NotifyURL).
Set("return_url", params.ReturnURL).
SetBodyMap("amount", func(bm gopay.BodyMap) { SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", amount). bm.Set("total", params.TotalFee).
Set("currency", "CNY") Set("currency", "CNY")
}) })
@ -71,22 +81,23 @@ func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject s
return wxRsp.Response.CodeUrl, nil return wxRsp.Response.CodeUrl, nil
} }
func (s *WechatPayService) PayUrlH5(outTradeNo string, amount int, subject string, ip string) (string, error) { func (s *WechatPayService) PayUrlH5(params WechatPayParams) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339) expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap // 初始化 BodyMap
bm := make(gopay.BodyMap) bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId). bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId). Set("mchid", s.config.MchId).
Set("description", subject). Set("description", params.Subject).
Set("out_trade_no", outTradeNo). Set("out_trade_no", params.OutTradeNo).
Set("time_expire", expire). Set("time_expire", expire).
Set("notify_url", s.config.NotifyURL). Set("notify_url", params.NotifyURL).
Set("return_url", params.ReturnURL).
SetBodyMap("amount", func(bm gopay.BodyMap) { SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", amount). bm.Set("total", params.TotalFee).
Set("currency", "CNY") Set("currency", "CNY")
}). }).
SetBodyMap("scene_info", func(bm gopay.BodyMap) { SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.Set("payer_client_ip", ip). bm.Set("payer_client_ip", params.ClientIP).
SetBodyMap("h5_info", func(bm gopay.BodyMap) { SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "Wap") bm.Set("type", "Wap")
}) })

View File

@ -181,6 +181,27 @@
.el-button { .el-button {
margin 10px 5px 0 5px margin 10px 5px 0 5px
padding 0
.icon-alipay,.icon-wechat-pay {
color #ffffff
}
.icon-qq {
color #15A6E8
font-size 24px
}
.icon-jd-pay {
color #ffffff
font-size 24px
}
.icon-douyin {
color #0a0a0a
font-size 22px
}
.icon-paypal {
font-size 14px
color #009CDE
}
} }
} }
} }

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=1726612860394') format('woff2'), src: url('iconfont.woff2?t=1726622198991') format('woff2'),
url('iconfont.woff?t=1726612860394') format('woff'), url('iconfont.woff?t=1726622198991') format('woff'),
url('iconfont.ttf?t=1726612860394') format('truetype'); url('iconfont.ttf?t=1726622198991') format('truetype');
} }
.iconfont { .iconfont {
@ -13,12 +13,12 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-douyin:before { .icon-paypal:before {
content: "\e852"; content: "\e666";
} }
.icon-paypal:before { .icon-douyin:before {
content: "\e60f"; content: "\e8db";
} }
.icon-qq:before { .icon-qq:before {

File diff suppressed because one or more lines are too long

View File

@ -6,18 +6,18 @@
"description": "", "description": "",
"glyphs": [ "glyphs": [
{ {
"icon_id": "22174321", "icon_id": "7443846",
"name": "抖音支付", "name": "PayPal",
"font_class": "douyin", "font_class": "paypal",
"unicode": "e852", "unicode": "e666",
"unicode_decimal": 59474 "unicode_decimal": 58982
}, },
{ {
"icon_id": "1238433", "icon_id": "18166694",
"name": "social-paypal", "name": "抖音",
"font_class": "paypal", "font_class": "douyin",
"unicode": "e60f", "unicode": "e8db",
"unicode_decimal": 58895 "unicode_decimal": 59611
}, },
{ {
"icon_id": "1244217", "icon_id": "1244217",

Binary file not shown.

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="user-bill" v-loading="loading"> <div class="user-bill" v-loading="loading" element-loading-background="rgba(255,255,255,.3)">
<el-row v-if="items.length > 0"> <el-row v-if="items.length > 0">
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border <el-table :data="items" :row-key="row => row.id" table-layout="auto" border
style="--el-table-border-color:#373C47; style="--el-table-border-color:#373C47;

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="member custom-scroll"> <div class="member custom-scroll" v-loading="loading" element-loading-background="rgba(255,255,255,.3)" :element-loading-text="loadingText">
<div class="inner"> <div class="inner">
<div class="user-profile"> <div class="user-profile">
<user-profile :key="profileKey"/> <user-profile :key="profileKey"/>
@ -22,10 +22,6 @@
<el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换 <el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
</el-button> </el-button>
</el-col> </el-col>
<el-col :span="24" style="padding-top: 30px" v-if="isLogin">
<el-button type="danger" round @click="logout">退出登录</el-button>
</el-col>
</el-row> </el-row>
</div> </div>
@ -67,23 +63,23 @@
<div class="pay-way"> <div class="pay-way">
<span type="primary" v-for="payWay in payWays" @click="genPayQrcode(item,payWay)" :key="payWay"> <span type="primary" v-for="payWay in payWays" @click="pay(item,payWay)" :key="payWay">
<el-button v-if="payWay.pay_type==='alipay'" color="#409eff" circle> <el-button v-if="payWay.pay_type==='alipay'" color="#15A6E8" circle>
<i class="iconfont icon-alipay" ></i> <i class="iconfont icon-alipay" ></i>
</el-button> </el-button>
<el-button v-else-if="payWay.pay_type==='qq'" class="qq" circle> <el-button v-else-if="payWay.pay_type==='qqpay'" circle>
<i class="iconfont icon-qq" ></i> <i class="iconfont icon-qq"></i>
</el-button> </el-button>
<el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" circle> <el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" round>
<i class="iconfont icon-paypal"></i> <i class="iconfont icon-paypal"></i>
</el-button> </el-button>
<el-button v-else-if="payWay.pay_type==='jd'" class="jd" circle> <el-button v-else-if="payWay.pay_type==='jdpay'" color="#E1251B" circle>
<i class="iconfont icon-jd-pay"></i> <i class="iconfont icon-jd-pay"></i>
</el-button> </el-button>
<el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle> <el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
<i class="iconfont icon-douyin"></i> <i class="iconfont icon-douyin"></i>
</el-button> </el-button>
<el-button v-else circle class="wechat"> <el-button v-else circle class="wechat" color="#67C23A">
<i class="iconfont icon-wechat-pay"></i> <i class="iconfont icon-wechat-pay"></i>
</el-button> </el-button>
</span> </span>
@ -92,7 +88,7 @@
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-empty description="暂无数据" /> <el-empty description="暂无数据" v-else />
<h2 class="headline">消费账单</h2> <h2 class="headline">消费账单</h2>
@ -102,47 +98,12 @@
</div> </div>
</div> </div>
<password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false" <password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"/>
@logout="logout"/>
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/> <bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
<bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/> <bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
<third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/> <third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
<redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/> <redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
<el-dialog
v-model="showPayDialog"
:close-on-click-modal="false"
:show-close="true"
:width="400"
@close="closeOrder"
:title="'实付金额:¥'+amount">
<div class="pay-container">
<div class="count-down">
<count-down :second="orderTimeout" @timeout="refreshPayCode" ref="countDownRef"/>
</div>
<div class="pay-qrcode" v-loading="loading">
<el-image :src="qrcode"/>
</div>
<div class="tip success" v-if="text !== ''">
<el-icon>
<SuccessFilled/>
</el-icon>
<span class="text">{{ text }}</span>
</div>
<div class="tip" v-else>
<el-icon>
<InfoFilled/>
</el-icon>
<span class="text">请打开手机扫码支付</span>
</div>
</div>
</el-dialog>
</div> </div>
</div> </div>
@ -151,46 +112,34 @@
<script setup> <script setup>
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} from "@/utils/http";
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";
import PasswordDialog from "@/components/PasswordDialog.vue"; import PasswordDialog from "@/components/PasswordDialog.vue";
import BindMobile from "@/components/BindMobile.vue"; import BindMobile from "@/components/BindMobile.vue";
import RedeemVerify from "@/components/RedeemVerify.vue"; import RedeemVerify from "@/components/RedeemVerify.vue";
import {useRouter} from "vue-router";
import {removeUserToken} from "@/store/session";
import UserOrder from "@/components/UserOrder.vue"; import UserOrder from "@/components/UserOrder.vue";
import CountDown from "@/components/CountDown.vue";
import {useSharedStore} from "@/store/sharedata"; import {useSharedStore} from "@/store/sharedata";
import BindEmail from "@/components/BindEmail.vue"; import BindEmail from "@/components/BindEmail.vue";
import ThirdLogin from "@/components/ThirdLogin.vue"; import ThirdLogin from "@/components/ThirdLogin.vue";
const list = ref([]) const list = ref([])
const showPayDialog = ref(false)
const vipImg = ref("/images/vip.png") const vipImg = ref("/images/vip.png")
const enableReward = ref(false) // const enableReward = ref(false) //
const rewardImg = ref('/images/reward.png') const rewardImg = ref('/images/reward.png')
const qrcode = ref("")
const showPasswordDialog = ref(false) const showPasswordDialog = ref(false)
const showBindMobileDialog = ref(false) const showBindMobileDialog = ref(false)
const showBindEmailDialog = ref(false) const showBindEmailDialog = ref(false)
const showRedeemVerifyDialog = ref(false) const showRedeemVerifyDialog = ref(false)
const showThirdLoginDialog = ref(false) const showThirdLoginDialog = ref(false)
const text = ref("")
const user = ref(null) const user = ref(null)
const isLogin = ref(false) const isLogin = ref(false)
const router = useRouter()
const activeOrderNo = ref("")
const countDownRef = ref(null)
const orderTimeout = ref(1800) const orderTimeout = ref(1800)
const loading = ref(true) const loading = ref(true)
const loadingText = ref("加载中...")
const orderPayInfoText = ref("") const orderPayInfoText = ref("")
const payWays = ref([]) const payWays = ref([])
const amount = ref(0)
const curPayProduct = ref(null)
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)
@ -206,6 +155,7 @@ onMounted(() => {
httpGet("/api/product/list").then((res) => { httpGet("/api/product/list").then((res) => {
list.value = res.data list.value = res.data
loading.value = false
}).catch(e => { }).catch(e => {
ElMessage.error("获取产品套餐失败:" + e.message) ElMessage.error("获取产品套餐失败:" + e.message)
}) })
@ -229,74 +179,30 @@ onMounted(() => {
}) })
}) })
// refresh payment qrcode const pay = (product, payWay) => {
const refreshPayCode = () => {
genPayQrcode(curPayProduct.value, curPayWay.value)
}
const genPayQrcode = (product, payWay) => {
if (!isLogin.value) { if (!isLogin.value) {
store.setShowLoginDialog(true) store.setShowLoginDialog(true)
return return
} }
loading.value = true loading.value = true
text.value = "" loadingText.value = "正在生成支付订单..."
curPayProduct.value = product httpGet(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
curPayWay.value = payWay product_id: product.id,
amount.value = (product.price - product.discount).toFixed(2)
httpPost("/api/payment/qrcode", {
pay_way: payWay.pay_way, pay_way: payWay.pay_way,
pay_type: payWay.pay_type, pay_type: payWay.pay_type,
product_id: curPayProduct.value.id, user_id: user.value.id,
device: "jump"
}).then(res => { }).then(res => {
showPayDialog.value = true window.open(res.data, '_blank');
qrcode.value = res.data['image']
activeOrderNo.value = res.data['order_no']
queryOrder(activeOrderNo.value)
loading.value = false loading.value = false
//
if (countDownRef.value) {
countDownRef.value.resetTimer()
}
}).catch(e => { }).catch(e => {
ElMessage.error("生成支付订单失败:" + e.message) setTimeout(() => {
ElMessage.error("生成支付订单失败:" + e.message)
loading.value = false
}, 500)
}) })
} }
const queryOrder = (orderNo) => {
httpGet("/api/order/query", {order_no: orderNo}).then(res => {
if (res.data.status === 1) {
text.value = "扫码成功,请在手机上进行支付!"
queryOrder(orderNo)
} else if (res.data.status === 2) {
text.value = "支付成功,正在刷新页面"
setTimeout(() => location.reload(), 500)
} else {
//
if (activeOrderNo.value === orderNo) {
queryOrder(orderNo)
}
}
}).catch(e => {
ElMessage.error("查询支付状态失败:" + e.message)
})
}
const logout = function () {
httpGet('/api/user/logout').then(() => {
removeUserToken();
router.push('/login');
}).catch(() => {
ElMessage.error('注销失败!');
})
}
const closeOrder = () => {
activeOrderNo.value = ''
}
const redeemCallback = (success) => { const redeemCallback = (success) => {
showRedeemVerifyDialog.value = false showRedeemVerifyDialog.value = false
if (success) { if (success) {