mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: add HuPiPay payment support
This commit is contained in:
		@@ -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,6 +239,7 @@ func parameterHandlerMiddleware() gin.HandlerFunc {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if strings.Contains(contentType, "application/json") {
 | 
			
		||||
			// process POST JSON request body
 | 
			
		||||
			bodyBytes, err := io.ReadAll(c.Request.Body)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -258,6 +260,7 @@ func parameterHandlerMiddleware() gin.HandlerFunc {
 | 
			
		||||
			trimJSONStrings(jsonData)
 | 
			
		||||
			// 更新请求体
 | 
			
		||||
			c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Next()
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ type AppConfig struct {
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -21,31 +21,37 @@ import (
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	PayWayAlipay = "支付宝"
 | 
			
		||||
	PayWayWechat = "微信支付"
 | 
			
		||||
	PayWayXunHu  = "虎皮椒"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PaymentHandler 支付服务回调 handler
 | 
			
		||||
type PaymentHandler struct {
 | 
			
		||||
	BaseHandler
 | 
			
		||||
	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,6 +66,7 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	// 更新扫码状态
 | 
			
		||||
	h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
 | 
			
		||||
	if payWay == "alipay" { // 支付宝
 | 
			
		||||
		// 生成支付链接
 | 
			
		||||
		notifyURL := h.App.Config.AlipayConfig.NotifyURL
 | 
			
		||||
		returnURL := "" // 关闭同步回跳
 | 
			
		||||
@@ -72,9 +79,48 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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":   "",
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
// OrderQuery 清单状态查询
 | 
			
		||||
		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 查询订单状态
 | 
			
		||||
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		OrderNo string `json:"order_no"`
 | 
			
		||||
@@ -111,14 +157,10 @@ 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 {
 | 
			
		||||
		PayWay    string `json:"pay_way"` // 支付方式
 | 
			
		||||
		ProductId uint   `json:"product_id"`
 | 
			
		||||
		UserId    int    `json:"user_id"`
 | 
			
		||||
	}
 | 
			
		||||
@@ -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")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								api/handler/test_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								api/handler/test_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								api/res/img/wechat-pay.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								api/res/img/wechat-pay.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 5.7 KiB  | 
							
								
								
									
										72
									
								
								api/service/payment/hupipay_serive.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								api/service/payment/hupipay_serive.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
@@ -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";
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -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": "笑脸",
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -41,7 +41,7 @@
 | 
			
		||||
 | 
			
		||||
            <ItemList :items="list" v-if="list.length > 0" :gap="30" :width="240">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <div class="product-item" :style="{width: scope.width+'px'}" @click="orderPay(scope.item)">
 | 
			
		||||
                <div class="product-item" :style="{width: scope.width+'px'}">
 | 
			
		||||
                  <div class="image-container">
 | 
			
		||||
                    <el-image :src="vipImg" fit="cover"/>
 | 
			
		||||
                  </div>
 | 
			
		||||
@@ -62,6 +62,16 @@
 | 
			
		||||
                      <span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
 | 
			
		||||
                      <span class="expire" v-else>当月有效</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <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>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </template>
 | 
			
		||||
@@ -113,7 +123,7 @@
 | 
			
		||||
        title="充值订单支付">
 | 
			
		||||
      <div class="pay-container">
 | 
			
		||||
        <div class="count-down">
 | 
			
		||||
          <count-down :second="orderTimeout" @timeout="orderPay" ref="countDown"/>
 | 
			
		||||
          <count-down :second="orderTimeout" @timeout="refreshPayCode" ref="countDownRef"/>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="pay-qrcode" v-loading="loading">
 | 
			
		||||
@@ -130,9 +140,10 @@
 | 
			
		||||
          <el-icon>
 | 
			
		||||
            <InfoFilled/>
 | 
			
		||||
          </el-icon>
 | 
			
		||||
          <span class="text">请打开手机支付宝扫码支付</span>
 | 
			
		||||
          <span class="text">请打开手机{{ payName }}扫码支付</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -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()
 | 
			
		||||
  genPayQrcode()
 | 
			
		||||
}
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("生成支付订单失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
// 虎皮椒支付
 | 
			
		||||
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 {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user