mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-18 17:26:38 +08:00
feat: add HuPiPay payment support
This commit is contained in:
parent
cf4dcc34ec
commit
4a9f7e3bce
@ -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/mj/jobs" ||
|
||||||
c.Request.URL.Path == "/api/invite/hits" ||
|
c.Request.URL.Path == "/api/invite/hits" ||
|
||||||
c.Request.URL.Path == "/api/sd/jobs" ||
|
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/sms/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/payment/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/payment/") ||
|
||||||
@ -238,6 +239,7 @@ func parameterHandlerMiddleware() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(contentType, "application/json") {
|
||||||
// process POST JSON request body
|
// process POST JSON request body
|
||||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -258,6 +260,7 @@ func parameterHandlerMiddleware() gin.HandlerFunc {
|
|||||||
trimJSONStrings(jsonData)
|
trimJSONStrings(jsonData)
|
||||||
// 更新请求体
|
// 更新请求体
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
|
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
|
||||||
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ type AppConfig struct {
|
|||||||
|
|
||||||
XXLConfig XXLConfig
|
XXLConfig XXLConfig
|
||||||
AlipayConfig AlipayConfig
|
AlipayConfig AlipayConfig
|
||||||
|
HuPiPayConfig HuPiPayConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatPlusApiConfig struct {
|
type ChatPlusApiConfig struct {
|
||||||
@ -40,10 +41,6 @@ type MidJourneyConfig struct {
|
|||||||
ChanelId string // Chanel ID
|
ChanelId string // Chanel ID
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeChatConfig struct {
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type StableDiffusionConfig struct {
|
type StableDiffusionConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ApiURL string
|
ApiURL string
|
||||||
@ -61,7 +58,7 @@ type AliYunSmsConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlipayConfig struct {
|
type AlipayConfig struct {
|
||||||
Enabled bool // 是否启用该服务
|
Enabled bool // 是否启用该支付通道
|
||||||
SandBox bool // 是否沙盒环境
|
SandBox bool // 是否沙盒环境
|
||||||
AppId string // 应用 ID
|
AppId string // 应用 ID
|
||||||
UserId string // 支付宝用户 ID
|
UserId string // 支付宝用户 ID
|
||||||
@ -72,6 +69,15 @@ type AlipayConfig struct {
|
|||||||
NotifyURL string // 异步通知回调
|
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 任务调度配置
|
type XXLConfig struct { // XXL 任务调度配置
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ServerAddr string
|
ServerAddr string
|
||||||
|
@ -44,6 +44,8 @@ func (h *OrderHandler) List(c *gin.Context) {
|
|||||||
end := utils.Str2stamp(data.PayTime[1] + " 00:00:00")
|
end := utils.Str2stamp(data.PayTime[1] + " 00:00:00")
|
||||||
session = session.Where("pay_time >= ? AND pay_time <= ?", start, end)
|
session = session.Where("pay_time >= ? AND pay_time <= ?", start, end)
|
||||||
}
|
}
|
||||||
|
session = session.Where("status = ?", types.OrderPaidSuccess)
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
session.Model(&model.Order{}).Count(&total)
|
session.Model(&model.Order{}).Count(&total)
|
||||||
var items []model.Order
|
var items []model.Order
|
||||||
|
@ -21,31 +21,37 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
PayWayAlipay = "支付宝"
|
PayWayAlipay = "支付宝"
|
||||||
PayWayWechat = "微信支付"
|
PayWayXunHu = "虎皮椒"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PaymentHandler 支付服务回调 handler
|
// PaymentHandler 支付服务回调 handler
|
||||||
type PaymentHandler struct {
|
type PaymentHandler struct {
|
||||||
BaseHandler
|
BaseHandler
|
||||||
alipayService *payment.AlipayService
|
alipayService *payment.AlipayService
|
||||||
|
huPiPayService *payment.HuPiPayService
|
||||||
snowflake *service.Snowflake
|
snowflake *service.Snowflake
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
fs embed.FS
|
fs embed.FS
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler {
|
func NewPaymentHandler(server *core.AppServer, alipayService *payment.AlipayService, huPiPayService *payment.HuPiPayService, snowflake *service.Snowflake, db *gorm.DB, fs embed.FS) *PaymentHandler {
|
||||||
h := PaymentHandler{lock: sync.Mutex{}}
|
h := PaymentHandler{
|
||||||
|
alipayService: alipayService,
|
||||||
|
huPiPayService: huPiPayService,
|
||||||
|
snowflake: snowflake,
|
||||||
|
fs: fs,
|
||||||
|
db: db,
|
||||||
|
lock: sync.Mutex{},
|
||||||
|
}
|
||||||
h.App = server
|
h.App = server
|
||||||
h.alipayService = alipayService
|
|
||||||
h.snowflake = snowflake
|
|
||||||
h.db = db
|
|
||||||
h.fs = fs
|
|
||||||
return &h
|
return &h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PaymentHandler) Alipay(c *gin.Context) {
|
func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||||
orderNo := h.GetTrim(c, "order_no")
|
orderNo := h.GetTrim(c, "order_no")
|
||||||
|
payWay := h.GetTrim(c, "pay_way")
|
||||||
|
|
||||||
if orderNo == "" {
|
if orderNo == "" {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
@ -60,6 +66,7 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
|
|||||||
|
|
||||||
// 更新扫码状态
|
// 更新扫码状态
|
||||||
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
|
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
|
||||||
|
if payWay == "alipay" { // 支付宝
|
||||||
// 生成支付链接
|
// 生成支付链接
|
||||||
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
notifyURL := h.App.Config.AlipayConfig.NotifyURL
|
||||||
returnURL := "" // 关闭同步回跳
|
returnURL := "" // 关闭同步回跳
|
||||||
@ -72,9 +79,48 @@ func (h *PaymentHandler) Alipay(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(302, uri)
|
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) {
|
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
OrderNo string `json:"order_no"`
|
OrderNo string `json:"order_no"`
|
||||||
@ -111,14 +157,10 @@ func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
|||||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlipayQrcode 生成支付宝支付 URL 二维码
|
// PayQrcode 生成支付 URL 二维码
|
||||||
func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||||
if !h.App.SysConfig.EnabledAlipay || h.alipayService == nil {
|
|
||||||
resp.ERROR(c, "当前支付通道已经关闭,请联系管理员开通!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
|
PayWay string `json:"pay_way"` // 支付方式
|
||||||
ProductId uint `json:"product_id"`
|
ProductId uint `json:"product_id"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
}
|
}
|
||||||
@ -146,6 +188,10 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payWay := PayWayAlipay
|
||||||
|
if data.PayWay == "hupi" {
|
||||||
|
payWay = PayWayXunHu
|
||||||
|
}
|
||||||
// 创建订单
|
// 创建订单
|
||||||
remark := types.OrderRemark{
|
remark := types.OrderRemark{
|
||||||
Days: product.Days,
|
Days: product.Days,
|
||||||
@ -162,7 +208,7 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
Subject: product.Name,
|
Subject: product.Name,
|
||||||
Amount: product.Price - product.Discount,
|
Amount: product.Price - product.Discount,
|
||||||
Status: types.OrderNotPaid,
|
Status: types.OrderNotPaid,
|
||||||
PayWay: PayWayAlipay,
|
PayWay: payWay,
|
||||||
Remark: utils.JsonEncode(remark),
|
Remark: utils.JsonEncode(remark),
|
||||||
}
|
}
|
||||||
res = h.db.Create(&order)
|
res = h.db.Create(&order)
|
||||||
@ -171,19 +217,30 @@ func (h *PaymentHandler) AlipayQrcode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成二维码图片
|
var logo string
|
||||||
file, err := h.fs.Open("res/img/alipay.jpg")
|
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 {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, "error with open qrcode log file: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL)
|
parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
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)
|
imgData, err := utils.GenQrcode(imageURL, 400, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
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})
|
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) {
|
func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||||
err := c.Request.ParseForm()
|
err := c.Request.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -212,27 +270,46 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
|||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
defer h.lock.Unlock()
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
var order model.Order
|
err = h.notify(r.OutTradeNo)
|
||||||
res := h.db.Where("order_no = ?", r.OutTradeNo).First(&order)
|
if err != nil {
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error(res.Error)
|
|
||||||
c.String(http.StatusOK, "fail")
|
c.String(http.StatusOK, "fail")
|
||||||
return
|
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
|
var user model.User
|
||||||
res = h.db.First(&user, order.UserId)
|
res = h.db.First(&user, order.UserId)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
logger.Error(res.Error)
|
err := fmt.Errorf("error with fetch user info: %v", res.Error)
|
||||||
c.String(http.StatusOK, "fail")
|
logger.Error(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var remark types.OrderRemark
|
var remark types.OrderRemark
|
||||||
err = utils.JsonDecode(order.Remark, &remark)
|
err := utils.JsonDecode(order.Remark, &remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(res.Error)
|
err := fmt.Errorf("error with decode order remark: %v", err)
|
||||||
c.String(http.StatusOK, "fail")
|
logger.Error(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 点卡:days == 0, calls > 0
|
// 1. 点卡:days == 0, calls > 0
|
||||||
// 2. vip 套餐:days > 0, calls == 0
|
// 2. vip 套餐:days > 0, calls == 0
|
||||||
if remark.Days > 0 {
|
if remark.Days > 0 {
|
||||||
@ -256,18 +333,57 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
|||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
res = h.db.Updates(&user)
|
res = h.db.Updates(&user)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
logger.Error(res.Error)
|
err := fmt.Errorf("error with update user info: %v", res.Error)
|
||||||
c.String(http.StatusOK, "fail")
|
logger.Error(err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新订单状态
|
// 更新订单状态
|
||||||
order.PayTime = time.Now().Unix()
|
order.PayTime = time.Now().Unix()
|
||||||
order.Status = types.OrderPaidSuccess
|
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))
|
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")
|
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.NewAlipayService),
|
||||||
|
fx.Provide(payment.NewHuPiPay),
|
||||||
fx.Provide(service.NewSnowflake),
|
fx.Provide(service.NewSnowflake),
|
||||||
fx.Provide(service.NewXXLJobExecutor),
|
fx.Provide(service.NewXXLJobExecutor),
|
||||||
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
|
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
|
||||||
@ -318,10 +319,12 @@ 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("alipay", h.Alipay)
|
group.GET("doPay", h.DoPay)
|
||||||
|
group.GET("payWays", h.GetPayWays)
|
||||||
group.POST("query", h.OrderQuery)
|
group.POST("query", h.OrderQuery)
|
||||||
group.POST("alipay/qrcode", h.AlipayQrcode)
|
group.POST("qrcode", h.PayQrcode)
|
||||||
group.POST("alipay/notify", h.AlipayNotify)
|
group.POST("alipay/notify", h.AlipayNotify)
|
||||||
|
group.POST("hupipay/notify", h.HuPiPayNotify)
|
||||||
}),
|
}),
|
||||||
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/")
|
||||||
@ -353,6 +356,11 @@ func main() {
|
|||||||
group.GET("hits", h.Hits)
|
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) {
|
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||||
err := s.Run(db)
|
err := s.Run(db)
|
||||||
if err != nil {
|
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-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1697164072791') format('woff2'),
|
src: url('iconfont.woff2?t=1702024026523') format('woff2'),
|
||||||
url('iconfont.woff?t=1697164072791') format('woff'),
|
url('iconfont.woff?t=1702024026523') format('woff'),
|
||||||
url('iconfont.ttf?t=1697164072791') format('truetype');
|
url('iconfont.ttf?t=1702024026523') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -13,6 +13,10 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-alipay:before {
|
||||||
|
content: "\e634";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-face:before {
|
.icon-face:before {
|
||||||
content: "\e64b";
|
content: "\e64b";
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -5,6 +5,13 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "1486848",
|
||||||
|
"name": "支付宝支付",
|
||||||
|
"font_class": "alipay",
|
||||||
|
"unicode": "e634",
|
||||||
|
"unicode_decimal": 58932
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "845789",
|
"icon_id": "845789",
|
||||||
"name": "笑脸",
|
"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">
|
<ItemList :items="list" v-if="list.length > 0" :gap="30" :width="240">
|
||||||
<template #default="scope">
|
<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">
|
<div class="image-container">
|
||||||
<el-image :src="vipImg" fit="cover"/>
|
<el-image :src="vipImg" fit="cover"/>
|
||||||
</div>
|
</div>
|
||||||
@ -62,6 +62,16 @@
|
|||||||
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
|
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
|
||||||
<span class="expire" v-else>当月有效</span>
|
<span class="expire" v-else>当月有效</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -113,7 +123,7 @@
|
|||||||
title="充值订单支付">
|
title="充值订单支付">
|
||||||
<div class="pay-container">
|
<div class="pay-container">
|
||||||
<div class="count-down">
|
<div class="count-down">
|
||||||
<count-down :second="orderTimeout" @timeout="orderPay" ref="countDown"/>
|
<count-down :second="orderTimeout" @timeout="refreshPayCode" ref="countDownRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pay-qrcode" v-loading="loading">
|
<div class="pay-qrcode" v-loading="loading">
|
||||||
@ -130,9 +140,10 @@
|
|||||||
<el-icon>
|
<el-icon>
|
||||||
<InfoFilled/>
|
<InfoFilled/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span class="text">请打开手机支付宝扫码支付</span>
|
<span class="text">请打开手机{{ payName }}扫码支付</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -152,6 +163,7 @@ import RewardVerify from "@/components/RewardVerify.vue";
|
|||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {removeUserToken} from "@/store/session";
|
import {removeUserToken} from "@/store/session";
|
||||||
import UserOrder from "@/components/UserOrder.vue";
|
import UserOrder from "@/components/UserOrder.vue";
|
||||||
|
import CountDown from "@/components/CountDown.vue";
|
||||||
|
|
||||||
const listBoxHeight = window.innerHeight - 97
|
const listBoxHeight = window.innerHeight - 97
|
||||||
const list = ref([])
|
const list = ref([])
|
||||||
@ -171,11 +183,15 @@ const isLogin = ref(false)
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const curPayProduct = ref(null)
|
const curPayProduct = ref(null)
|
||||||
const activeOrderNo = ref("")
|
const activeOrderNo = ref("")
|
||||||
const countDown = ref(null)
|
const countDownRef = ref(null)
|
||||||
const orderTimeout = ref(1800)
|
const orderTimeout = ref(1800)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const orderPayInfoText = ref("")
|
const orderPayInfoText = ref("")
|
||||||
|
|
||||||
|
const payWays = ref({})
|
||||||
|
const payName = ref("支付宝")
|
||||||
|
const curPay = ref("alipay") // 当前支付方式
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkSession().then(_user => {
|
checkSession().then(_user => {
|
||||||
@ -200,9 +216,48 @@ onMounted(() => {
|
|||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
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) {
|
if (!user.value.id) {
|
||||||
showLoginDialog.value = true
|
showLoginDialog.value = true
|
||||||
return
|
return
|
||||||
@ -210,21 +265,22 @@ const orderPay = (row) => {
|
|||||||
if (row) {
|
if (row) {
|
||||||
curPayProduct.value = row
|
curPayProduct.value = row
|
||||||
}
|
}
|
||||||
loading.value = true
|
genPayQrcode()
|
||||||
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)
|
// 虎皮椒支付
|
||||||
})
|
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) => {
|
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 {
|
&:hover {
|
||||||
|
Loading…
Reference in New Issue
Block a user