mirror of
https://gitee.com/blackfox/geekai.git
synced 2024-12-04 21:27:39 +08:00
274 lines
6.5 KiB
Go
274 lines
6.5 KiB
Go
package handler
|
||
|
||
import (
|
||
"chatplus/core"
|
||
"chatplus/core/types"
|
||
"chatplus/service"
|
||
"chatplus/service/payment"
|
||
"chatplus/store/model"
|
||
"chatplus/utils"
|
||
"chatplus/utils/resp"
|
||
"embed"
|
||
"encoding/base64"
|
||
"fmt"
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
"net/http"
|
||
"net/url"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
PayWayAlipay = "支付宝"
|
||
PayWayWechat = "微信支付"
|
||
)
|
||
|
||
// PaymentHandler 支付服务回调 handler
|
||
type PaymentHandler struct {
|
||
BaseHandler
|
||
alipayService *payment.AlipayService
|
||
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{}}
|
||
h.App = server
|
||
h.alipayService = alipayService
|
||
h.snowflake = snowflake
|
||
h.db = db
|
||
h.fs = fs
|
||
return &h
|
||
}
|
||
|
||
func (h *PaymentHandler) Alipay(c *gin.Context) {
|
||
orderNo := h.GetTrim(c, "order_no")
|
||
if orderNo == "" {
|
||
resp.ERROR(c, types.InvalidArgs)
|
||
return
|
||
}
|
||
|
||
var order model.Order
|
||
res := h.db.Where("order_no = ?", orderNo).First(&order)
|
||
if res.Error != nil {
|
||
resp.ERROR(c, "Order not found")
|
||
return
|
||
}
|
||
|
||
// 更新扫码状态
|
||
h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
|
||
// 生成支付链接
|
||
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())
|
||
return
|
||
}
|
||
|
||
c.Redirect(302, uri)
|
||
}
|
||
|
||
// OrderQuery 清单状态查询
|
||
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
||
var data struct {
|
||
OrderNo string `json:"order_no"`
|
||
}
|
||
if err := c.ShouldBindJSON(&data); err != nil {
|
||
resp.ERROR(c, types.InvalidArgs)
|
||
return
|
||
}
|
||
|
||
var order model.Order
|
||
res := h.db.Where("order_no = ?", data.OrderNo).First(&order)
|
||
if res.Error != nil {
|
||
resp.ERROR(c, "Order not found")
|
||
return
|
||
}
|
||
|
||
if order.Status == types.OrderPaidSuccess {
|
||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||
return
|
||
}
|
||
|
||
counter := 0
|
||
for {
|
||
time.Sleep(time.Second)
|
||
var item model.Order
|
||
h.db.Where("order_no = ?", data.OrderNo).First(&item)
|
||
if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
|
||
order.Status = item.Status
|
||
break
|
||
}
|
||
counter++
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
var data struct {
|
||
ProductId uint `json:"product_id"`
|
||
UserId int `json:"user_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()
|
||
if err != nil {
|
||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||
return
|
||
}
|
||
var user model.User
|
||
res = h.db.First(&user, data.UserId)
|
||
if res.Error != nil {
|
||
resp.ERROR(c, "Invalid user ID")
|
||
return
|
||
}
|
||
|
||
// 创建订单
|
||
remark := types.OrderRemark{
|
||
Days: product.Days,
|
||
Calls: product.Calls,
|
||
Name: product.Name,
|
||
Price: product.Price,
|
||
Discount: product.Discount,
|
||
}
|
||
order := model.Order{
|
||
UserId: user.Id,
|
||
Mobile: user.Mobile,
|
||
ProductId: product.Id,
|
||
OrderNo: orderNo,
|
||
Subject: product.Name,
|
||
Amount: product.Price - product.Discount,
|
||
Status: types.OrderNotPaid,
|
||
PayWay: PayWayAlipay,
|
||
Remark: utils.JsonEncode(remark),
|
||
}
|
||
res = h.db.Create(&order)
|
||
if res.Error != nil {
|
||
resp.ERROR(c, "error with create order: "+res.Error.Error())
|
||
return
|
||
}
|
||
|
||
// 生成二维码图片
|
||
file, err := h.fs.Open("res/img/alipay.jpg")
|
||
if err != nil {
|
||
resp.ERROR(c, 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)
|
||
imgData, err := utils.GenQrcode(imageURL, 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": imageURL})
|
||
}
|
||
|
||
func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||
err := c.Request.ParseForm()
|
||
if err != nil {
|
||
c.String(http.StatusOK, "fail")
|
||
return
|
||
}
|
||
|
||
// TODO:这里最好用支付宝的公钥签名签证一下交易真假
|
||
//res := h.alipayService.TradeVerify(c.Request.Form)
|
||
r := h.alipayService.TradeQuery(c.Request.Form.Get("out_trade_no"))
|
||
logger.Infof("验证支付结果:%+v", r)
|
||
if !r.Success() {
|
||
c.String(http.StatusOK, "fail")
|
||
return
|
||
}
|
||
|
||
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)
|
||
c.String(http.StatusOK, "fail")
|
||
return
|
||
}
|
||
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
|
||
}
|
||
var remark types.OrderRemark
|
||
err = utils.JsonDecode(order.Remark, &remark)
|
||
if err != nil {
|
||
logger.Error(res.Error)
|
||
c.String(http.StatusOK, "fail")
|
||
return
|
||
}
|
||
// 1. 点卡:days == 0, calls > 0
|
||
// 2. vip 套餐:days > 0, calls == 0
|
||
if remark.Days > 0 {
|
||
if user.ExpiredTime > time.Now().Unix() {
|
||
user.ExpiredTime = time.Unix(user.ExpiredTime, 0).AddDate(0, 0, remark.Days).Unix()
|
||
} else {
|
||
user.ExpiredTime = time.Now().AddDate(0, 0, remark.Days).Unix()
|
||
}
|
||
user.Vip = true
|
||
|
||
} else if !user.Vip { // 充值点卡的非 VIP 用户
|
||
user.ExpiredTime = time.Now().AddDate(0, 0, 30).Unix()
|
||
}
|
||
|
||
if remark.Calls > 0 { // 充值点卡
|
||
user.Calls += remark.Calls
|
||
} else {
|
||
user.Calls += h.App.SysConfig.VipMonthCalls
|
||
}
|
||
|
||
// 更新用户信息
|
||
res = h.db.Updates(&user)
|
||
if res.Error != nil {
|
||
logger.Error(res.Error)
|
||
c.String(http.StatusOK, "fail")
|
||
return
|
||
}
|
||
|
||
// 更新订单状态
|
||
order.PayTime = time.Now().Unix()
|
||
order.Status = types.OrderPaidSuccess
|
||
h.db.Updates(&order)
|
||
|
||
// 更新产品销量
|
||
h.db.Model(&model.Product{}).Where("id = ?", order.ProductId).UpdateColumn("sales", gorm.Expr("sales + ?", 1))
|
||
|
||
c.String(http.StatusOK, "success")
|
||
}
|