diff --git a/api/core/app_server.go b/api/core/app_server.go index 943f1cc4..f0b5d07a 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -156,6 +156,7 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc { c.Request.URL.Path == "/api/mj/jobs" || c.Request.URL.Path == "/api/invite/hits" || c.Request.URL.Path == "/api/sd/jobs" || + strings.HasPrefix(c.Request.URL.Path, "/test/") || strings.HasPrefix(c.Request.URL.Path, "/api/sms/") || strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") || strings.HasPrefix(c.Request.URL.Path, "/api/payment/") || @@ -238,26 +239,28 @@ func parameterHandlerMiddleware() gin.HandlerFunc { return } - // process POST JSON request body - bodyBytes, err := io.ReadAll(c.Request.Body) - if err != nil { - c.Next() - return - } + if strings.Contains(contentType, "application/json") { + // process POST JSON request body + bodyBytes, err := io.ReadAll(c.Request.Body) + if err != nil { + c.Next() + return + } - // 还原请求体 - c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - // 将请求体解析为 JSON - var jsonData map[string]interface{} - if err := c.ShouldBindJSON(&jsonData); err != nil { - c.Next() - return - } + // 还原请求体 + c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + // 将请求体解析为 JSON + var jsonData map[string]interface{} + if err := c.ShouldBindJSON(&jsonData); err != nil { + c.Next() + return + } - // 对 JSON 数据中的字符串值去除两端空格 - trimJSONStrings(jsonData) - // 更新请求体 - c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData))) + // 对 JSON 数据中的字符串值去除两端空格 + trimJSONStrings(jsonData) + // 更新请求体 + c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData))) + } c.Next() } diff --git a/api/core/types/config.go b/api/core/types/config.go index 1a337a63..59c89589 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -22,8 +22,9 @@ type AppConfig struct { WeChatBot bool // 是否启用微信机器人 SdConfig StableDiffusionConfig // sd 绘画配置 - XXLConfig XXLConfig - AlipayConfig AlipayConfig + XXLConfig XXLConfig + AlipayConfig AlipayConfig + HuPiPayConfig HuPiPayConfig } type ChatPlusApiConfig struct { @@ -40,10 +41,6 @@ type MidJourneyConfig struct { ChanelId string // Chanel ID } -type WeChatConfig struct { - Enabled bool -} - type StableDiffusionConfig struct { Enabled bool ApiURL string @@ -61,7 +58,7 @@ type AliYunSmsConfig struct { } type AlipayConfig struct { - Enabled bool // 是否启用该服务 + Enabled bool // 是否启用该支付通道 SandBox bool // 是否沙盒环境 AppId string // 应用 ID UserId string // 支付宝用户 ID @@ -72,6 +69,15 @@ type AlipayConfig struct { NotifyURL string // 异步通知回调 } +type HuPiPayConfig struct { //虎皮椒第四方支付配置 + Enabled bool // 是否启用该支付通道 + Name string // 支付名称,如:wechat/alipay + AppId string // App ID + AppSecret string // app 密钥 + NotifyURL string // 异步通知回调 + PayURL string // 支付网关 +} + type XXLConfig struct { // XXL 任务调度配置 Enabled bool ServerAddr string diff --git a/api/handler/admin/order_handler.go b/api/handler/admin/order_handler.go index 4ec4537a..229dd2a2 100644 --- a/api/handler/admin/order_handler.go +++ b/api/handler/admin/order_handler.go @@ -44,6 +44,8 @@ func (h *OrderHandler) List(c *gin.Context) { end := utils.Str2stamp(data.PayTime[1] + " 00:00:00") session = session.Where("pay_time >= ? AND pay_time <= ?", start, end) } + session = session.Where("status = ?", types.OrderPaidSuccess) + var total int64 session.Model(&model.Order{}).Count(&total) var items []model.Order diff --git a/api/handler/payment_handler.go b/api/handler/payment_handler.go index fb9d6ba8..a75e2ccd 100644 --- a/api/handler/payment_handler.go +++ b/api/handler/payment_handler.go @@ -21,31 +21,37 @@ import ( const ( PayWayAlipay = "支付宝" - PayWayWechat = "微信支付" + PayWayXunHu = "虎皮椒" ) // PaymentHandler 支付服务回调 handler type PaymentHandler struct { BaseHandler - alipayService *payment.AlipayService - snowflake *service.Snowflake - db *gorm.DB - fs embed.FS - lock sync.Mutex + alipayService *payment.AlipayService + huPiPayService *payment.HuPiPayService + snowflake *service.Snowflake + db *gorm.DB + fs embed.FS + lock sync.Mutex } -func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler { - h := PaymentHandler{lock: sync.Mutex{}} +func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, huPiPayService *payment.HuPiPayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler { + h := PaymentHandler{ + alipayService: alipayService, + huPiPayService: huPiPayService, + snowflake: snowflake, + fs: fs, + db: db, + lock: sync.Mutex{}, + } h.App = server - h.alipayService = alipayService - h.snowflake = snowflake - h.db = db - h.fs = fs return &h } -func (h *PaymentHandler) Alipay(c *gin.Context) { +func (h *PaymentHandler) DoPay(c *gin.Context) { orderNo := h.GetTrim(c, "order_no") + payWay := h.GetTrim(c, "pay_way") + if orderNo == "" { resp.ERROR(c, types.InvalidArgs) return @@ -60,21 +66,61 @@ func (h *PaymentHandler) Alipay(c *gin.Context) { // 更新扫码状态 h.db.Model(&order).UpdateColumn("status", types.OrderScanned) - // 生成支付链接 - notifyURL := h.App.Config.AlipayConfig.NotifyURL - returnURL := "" // 关闭同步回跳 - amount := fmt.Sprintf("%.2f", order.Amount) + if payWay == "alipay" { // 支付宝 + // 生成支付链接 + notifyURL := h.App.Config.AlipayConfig.NotifyURL + returnURL := "" // 关闭同步回跳 + amount := fmt.Sprintf("%.2f", order.Amount) - uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject) - if err != nil { - resp.ERROR(c, "error with generate pay url: "+err.Error()) + uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject) + if err != nil { + resp.ERROR(c, "error with generate pay url: "+err.Error()) + return + } + + c.Redirect(302, uri) return - } + } else if payWay == "hupi" { // 虎皮椒支付 + params := map[string]string{ + "version": "1.1", + "trade_order_id": orderNo, + "total_fee": fmt.Sprintf("%f", order.Amount), + "title": order.Subject, + "notify_url": h.App.Config.HuPiPayConfig.NotifyURL, + "return_url": "", + "wap_name": "极客学长", + "callback_url": "", + } - c.Redirect(302, uri) + res, err := h.huPiPayService.Pay(params) + if err != nil { + resp.ERROR(c, "error with generate pay url: "+err.Error()) + return + } + + var r struct { + Openid int64 `json:"openid"` + UrlQrcode string `json:"url_qrcode"` + URL string `json:"url"` + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + } + err = utils.JsonDecode(res, &r) + if err != nil { + resp.ERROR(c, "error with decode payment result: "+err.Error()) + return + } + + if r.ErrCode != 0 { + resp.ERROR(c, "error with generate pay url: "+r.ErrMsg) + return + } + c.Redirect(302, r.URL) + } + resp.ERROR(c, "Invalid operations") } -// OrderQuery 清单状态查询 +// OrderQuery 查询订单状态 func (h *PaymentHandler) OrderQuery(c *gin.Context) { var data struct { OrderNo string `json:"order_no"` @@ -111,16 +157,12 @@ func (h *PaymentHandler) OrderQuery(c *gin.Context) { resp.SUCCESS(c, gin.H{"status": order.Status}) } -// AlipayQrcode 生成支付宝支付 URL 二维码 -func (h *PaymentHandler) AlipayQrcode(c *gin.Context) { - if !h.App.SysConfig.EnabledAlipay || h.alipayService == nil { - resp.ERROR(c, "当前支付通道已经关闭,请联系管理员开通!") - return - } - +// PayQrcode 生成支付 URL 二维码 +func (h *PaymentHandler) PayQrcode(c *gin.Context) { var data struct { - ProductId uint `json:"product_id"` - UserId int `json:"user_id"` + PayWay string `json:"pay_way"` // 支付方式 + ProductId uint `json:"product_id"` + UserId int `json:"user_id"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -146,6 +188,10 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) { return } + payWay := PayWayAlipay + if data.PayWay == "hupi" { + payWay = PayWayXunHu + } // 创建订单 remark := types.OrderRemark{ Days: product.Days, @@ -162,7 +208,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) { Subject: product.Name, Amount: product.Price - product.Discount, Status: types.OrderNotPaid, - PayWay: PayWayAlipay, + PayWay: payWay, Remark: utils.JsonEncode(remark), } res = h.db.Create(&order) @@ -171,19 +217,30 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) { return } - // 生成二维码图片 - file, err := h.fs.Open("res/img/alipay.jpg") + var logo string + if data.PayWay == "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" + } + } + + file, err := h.fs.Open(logo) if err != nil { - resp.ERROR(c, err.Error()) + resp.ERROR(c, "error with open qrcode log file: "+err.Error()) return } + parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL) if err != nil { resp.ERROR(c, err.Error()) return } - imageURL := fmt.Sprintf("%s://%s/api/payment/alipay?order_no=%s", parse.Scheme, parse.Host, orderNo) + imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay) imgData, err := utils.GenQrcode(imageURL, 400, file) if err != nil { resp.ERROR(c, err.Error()) @@ -193,6 +250,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) { resp.SUCCESS(c, gin.H{"order_no": orderNo, "image": fmt.Sprintf("data:image/jpg;base64, %s", imgDataBase64), "url": imageURL}) } +// AlipayNotify 支付宝支付回调 func (h *PaymentHandler) AlipayNotify(c *gin.Context) { err := c.Request.ParseForm() if err != nil { @@ -212,27 +270,46 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) { h.lock.Lock() defer h.lock.Unlock() - var order model.Order - res := h.db.Where("order_no = ?", r.OutTradeNo).First(&order) - if res.Error != nil { - logger.Error(res.Error) + err = h.notify(r.OutTradeNo) + if err != nil { c.String(http.StatusOK, "fail") return } + + c.String(http.StatusOK, "success") +} + +// 异步通知回调公共逻辑 +func (h *PaymentHandler) notify(orderNo string) error { + var order model.Order + res := h.db.Where("order_no = ?", orderNo).First(&order) + if res.Error != nil { + err := fmt.Errorf("error with fetch order: %v", res.Error) + logger.Error(err) + return err + } + + // 已支付订单,直接返回 + if order.Status == types.OrderPaidSuccess { + return nil + } + var user model.User res = h.db.First(&user, order.UserId) if res.Error != nil { - logger.Error(res.Error) - c.String(http.StatusOK, "fail") - return + err := fmt.Errorf("error with fetch user info: %v", res.Error) + logger.Error(err) + return err } + var remark types.OrderRemark - err = utils.JsonDecode(order.Remark, &remark) + err := utils.JsonDecode(order.Remark, &remark) if err != nil { - logger.Error(res.Error) - c.String(http.StatusOK, "fail") - return + err := fmt.Errorf("error with decode order remark: %v", err) + logger.Error(err) + return err } + // 1. 点卡:days == 0, calls > 0 // 2. vip 套餐:days > 0, calls == 0 if remark.Days > 0 { @@ -256,18 +333,57 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) { // 更新用户信息 res = h.db.Updates(&user) if res.Error != nil { - logger.Error(res.Error) - c.String(http.StatusOK, "fail") - return + err := fmt.Errorf("error with update user info: %v", res.Error) + logger.Error(err) + return err } // 更新订单状态 order.PayTime = time.Now().Unix() order.Status = types.OrderPaidSuccess - h.db.Updates(&order) + res = h.db.Updates(&order) + if res.Error != nil { + err := fmt.Errorf("error with update order info: %v", res.Error) + logger.Error(err) + return err + } // 更新产品销量 h.db.Model(&model.Product{}).Where("id = ?", order.ProductId).UpdateColumn("sales", gorm.Expr("sales + ?", 1)) + return nil +} + +// GetPayWays 获取支付方式 +func (h *PaymentHandler) GetPayWays(c *gin.Context) { + data := gin.H{} + if h.App.Config.AlipayConfig.Enabled { + data["alipay"] = gin.H{"name": "alipay"} + } + if h.App.Config.HuPiPayConfig.Enabled { + data["hupi"] = gin.H{"name": h.App.Config.HuPiPayConfig.Name} + } + resp.SUCCESS(c, data) +} + +// HuPiPayNotify 虎皮椒支付异步回调 +func (h *PaymentHandler) HuPiPayNotify(c *gin.Context) { + err := c.Request.ParseForm() + if err != nil { + c.String(http.StatusOK, "fail") + return + } + + orderNo := c.Request.Form.Get("trade_order_id") + logger.Infof("收到订单支付回调,订单 NO:%s", orderNo) + // TODO 是否要保存订单交易流水号 + h.lock.Lock() + defer h.lock.Unlock() + + err = h.notify(orderNo) + if err != nil { + c.String(http.StatusOK, "fail") + return + } c.String(http.StatusOK, "success") } diff --git a/api/handler/test_handler.go b/api/handler/test_handler.go new file mode 100644 index 00000000..58d2a668 --- /dev/null +++ b/api/handler/test_handler.go @@ -0,0 +1,40 @@ +package handler + +import ( + "chatplus/service" + "github.com/gin-gonic/gin" +) + +type TestHandler struct { + snowflake *service.Snowflake +} + +func NewTestHandler(snowflake *service.Snowflake) *TestHandler { + return &TestHandler{snowflake: snowflake} +} + +func (h *TestHandler) TestPay(c *gin.Context) { + //appId := "" //Appid + //appSecret := "" //密钥 + //var host = "https://api.xunhupay.com/payment/do.html" //跳转支付页接口URL + //client := payment.NewXunHuPay(appId, appSecret) //初始化调用 + // + ////支付参数,appid、time、nonce_str和hash这四个参数不用传,调用的时候执行方法内部已经处理 + //orderNo, _ := h.snowflake.Next() + //params := map[string]string{ + // "version": "1.1", + // "trade_order_id": orderNo, + // "total_fee": "0.1", + // "title": "测试支付", + // "notify_url": "http://xxxxxxx.com", + // "return_url": "http://localhost:8888", + // "wap_name": "极客学长", + // "callback_url": "", + //} + // + //execute, err := client.Execute(host, params) //执行支付操作 + //if err != nil { + // logger.Error(err) + //} + //resp.SUCCESS(c, execute) +} diff --git a/api/main.go b/api/main.go index f81976fa..40116c1b 100644 --- a/api/main.go +++ b/api/main.go @@ -192,6 +192,7 @@ func main() { }), fx.Provide(payment.NewAlipayService), + fx.Provide(payment.NewHuPiPay), fx.Provide(service.NewSnowflake), fx.Provide(service.NewXXLJobExecutor), fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) { @@ -318,10 +319,12 @@ func main() { }), fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) { group := s.Engine.Group("/api/payment/") - group.GET("alipay", h.Alipay) + group.GET("doPay", h.DoPay) + group.GET("payWays", h.GetPayWays) group.POST("query", h.OrderQuery) - group.POST("alipay/qrcode", h.AlipayQrcode) + group.POST("qrcode", h.PayQrcode) group.POST("alipay/notify", h.AlipayNotify) + group.POST("hupipay/notify", h.HuPiPayNotify) }), fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) { group := s.Engine.Group("/api/admin/product/") @@ -353,6 +356,11 @@ func main() { group.GET("hits", h.Hits) }), + fx.Provide(handler.NewTestHandler), + fx.Invoke(func(s *core.AppServer, h *handler.TestHandler) { + group := s.Engine.Group("/test/") + group.GET("pay", h.TestPay) + }), fx.Invoke(func(s *core.AppServer, db *gorm.DB) { err := s.Run(db) if err != nil { diff --git a/api/res/img/wechat-pay.jpg b/api/res/img/wechat-pay.jpg new file mode 100644 index 00000000..db398396 Binary files /dev/null and b/api/res/img/wechat-pay.jpg differ diff --git a/api/service/payment/hupipay_serive.go b/api/service/payment/hupipay_serive.go new file mode 100644 index 00000000..d623a52b --- /dev/null +++ b/api/service/payment/hupipay_serive.go @@ -0,0 +1,72 @@ +package payment + +import ( + "chatplus/core/types" + "crypto/md5" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" +) + +type HuPiPayService struct { + appId string + appSecret string + host string +} + +func NewHuPiPay(config *types.AppConfig) *HuPiPayService { + return &HuPiPayService{ + appId: config.HuPiPayConfig.AppId, + appSecret: config.HuPiPayConfig.AppSecret, + host: config.HuPiPayConfig.PayURL, + } +} + +// Pay 执行支付请求操作 +func (s *HuPiPayService) Pay(params map[string]string) (string, error) { + data := url.Values{} + simple := strconv.FormatInt(time.Now().Unix(), 10) + params["appid"] = s.appId + params["time"] = simple + params["nonce_str"] = simple + for k, v := range params { + data.Add(k, v) + } + data.Add("hash", s.Sign(params)) + resp, err := http.PostForm(s.host, data) + if err != nil { + return "error", err + } + defer resp.Body.Close() + all, err := io.ReadAll(resp.Body) + if err != nil { + return "error", err + } + return string(all), err +} + +// Sign 签名方法 +func (s *HuPiPayService) Sign(params map[string]string) string { + var data string + keys := make([]string, 0, 0) + params["appid"] = s.appId + for key, _ := range params { + keys = append(keys, key) + } + sort.Strings(keys) + //拼接 + for _, k := range keys { + data = fmt.Sprintf("%s%s=%s&", data, k, params[k]) + } + data = strings.Trim(data, "&") + data = fmt.Sprintf("%s%s", data, s.appSecret) + m := md5.New() + m.Write([]byte(data)) + sign := fmt.Sprintf("%x", m.Sum(nil)) + return sign +} diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index d1a46df2..f9fb659a 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=1697164072791') format('woff2'), - url('iconfont.woff?t=1697164072791') format('woff'), - url('iconfont.ttf?t=1697164072791') format('truetype'); + src: url('iconfont.woff2?t=1702024026523') format('woff2'), + url('iconfont.woff?t=1702024026523') format('woff'), + url('iconfont.ttf?t=1702024026523') format('truetype'); } .iconfont { @@ -13,6 +13,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-alipay:before { + content: "\e634"; +} + .icon-face:before { content: "\e64b"; } diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index a2a5be82..ab90b064 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',function(a){var l=(l=document.getElementsByTagName("script"))[l.length-1],c=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var t,o,i,h,e,s=function(l,c){c.parentNode.insertBefore(l,c)};if(c&&!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?s(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),t()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(i=t,h=a.document,e=!1,z(),h.onreadystatechange=function(){"complete"==h.readyState&&(h.onreadystatechange=null,m())})}function m(){e||(e=!0,i())}function z(){try{h.documentElement.doScroll("left")}catch(l){return void setTimeout(z,50)}m()}}(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',function(a){var l=(l=document.getElementsByTagName("script"))[l.length-1],c=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var t,o,i,h,e,s=function(l,c){c.parentNode.insertBefore(l,c)};if(c&&!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?s(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),t()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(i=t,h=a.document,e=!1,z(),h.onreadystatechange=function(){"complete"==h.readyState&&(h.onreadystatechange=null,m())})}function m(){e||(e=!0,i())}function z(){try{h.documentElement.doScroll("left")}catch(l){return void setTimeout(z,50)}m()}}(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index 951e07cf..6e99ab74 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -5,6 +5,13 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "1486848", + "name": "支付宝支付", + "font_class": "alipay", + "unicode": "e634", + "unicode_decimal": 58932 + }, { "icon_id": "845789", "name": "笑脸", diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 4715297f..df9a3753 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 5d590d04..abfc99eb 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 4ec8bc72..3f951f4d 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 45750f56..dcf779b0 100644 --- a/web/src/views/Member.vue +++ b/web/src/views/Member.vue @@ -41,7 +41,7 @@ @@ -113,7 +123,7 @@ title="充值订单支付">
- +
@@ -130,9 +140,10 @@ - 请打开手机支付宝扫码支付 + 请打开手机{{ payName }}扫码支付
+ @@ -152,6 +163,7 @@ import RewardVerify from "@/components/RewardVerify.vue"; import {useRouter} from "vue-router"; import {removeUserToken} from "@/store/session"; import UserOrder from "@/components/UserOrder.vue"; +import CountDown from "@/components/CountDown.vue"; const listBoxHeight = window.innerHeight - 97 const list = ref([]) @@ -171,11 +183,15 @@ const isLogin = ref(false) const router = useRouter() const curPayProduct = ref(null) const activeOrderNo = ref("") -const countDown = ref(null) +const countDownRef = ref(null) const orderTimeout = ref(1800) const loading = ref(true) const orderPayInfoText = ref("") +const payWays = ref({}) +const payName = ref("支付宝") +const curPay = ref("alipay") // 当前支付方式 + onMounted(() => { checkSession().then(_user => { @@ -200,9 +216,48 @@ onMounted(() => { }).catch(e => { ElMessage.error("获取系统配置失败:" + e.message) }) + + httpGet("/api/payment/payWays").then(res => { + payWays.value = res.data + }).catch(e => { + ElMessage.error("获取支付方式失败:" + e.message) + }) }) -const orderPay = (row) => { +// refresh payment qrcode +const refreshPayCode = () => { + if (curPay.value === 'alipay') { + alipay() + } else if (curPay.value === 'hupi') { + huPiPay() + } +} + +const genPayQrcode = () => { + loading.value = true + text.value = "" + httpPost("/api/payment/qrcode", { + pay_way: curPay.value, + product_id: curPayProduct.value.id, + user_id: user.value.id + }).then(res => { + showPayDialog.value = true + qrcode.value = res.data['image'] + activeOrderNo.value = res.data['order_no'] + queryOrder(activeOrderNo.value) + loading.value = false + // 重置计数器 + if (countDownRef.value) { + countDownRef.value.resetTimer() + } + }).catch(e => { + ElMessage.error("生成支付订单失败:" + e.message) + }) +} + +const alipay = (row) => { + payName.value = "支付宝" + curPay.value = "alipay" if (!user.value.id) { showLoginDialog.value = true return @@ -210,21 +265,22 @@ const orderPay = (row) => { if (row) { curPayProduct.value = row } - loading.value = true - text.value = "" - httpPost("/api/payment/alipay/qrcode", {product_id: curPayProduct.value.id, user_id: user.value.id}).then(res => { - showPayDialog.value = true - qrcode.value = res.data['image'] - activeOrderNo.value = res.data['order_no'] - queryOrder(activeOrderNo.value) - loading.value = false - // 重置计数器 - if (countDown.value) { - countDown.value.resetTimer() - } - }).catch(e => { - ElMessage.error("生成支付订单失败:" + e.message) - }) + genPayQrcode() +} + +// 虎皮椒支付 +const huPiPay = (row) => { + payName.value = payWays.value["hupi"]["name"] === "wechat" ? '微信' : '支付宝' + curPay.value = "hupi" + if (!user.value.id) { + showLoginDialog.value = true + return + } + if (row) { + curPayProduct.value = row + } + genPayQrcode() + } const queryOrder = (orderNo) => { @@ -416,6 +472,16 @@ const logout = function () { } } + + .pay-way { + padding 10px 0 + display flex + justify-content: space-between + + .iconfont { + margin-right 5px + } + } } &:hover {