Files
platform/web/handlers/trade.go

366 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handlers
import (
"bufio"
"fmt"
"log/slog"
"platform/pkg/env"
"platform/pkg/u"
"platform/web/auth"
"platform/web/core"
g "platform/web/globals"
m "platform/web/models"
q "platform/web/queries"
s "platform/web/services"
"time"
"github.com/gofiber/fiber/v2"
"github.com/valyala/fasthttp"
)
// PageTradeByAdmin 分页查询所有订单
func PageTradeByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeRead)
if err != nil {
return err
}
// 解析请求参数
req := new(PageTradeByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构建查询语句
do := q.Trade.Where()
if req.UserPhone != nil {
do = do.Where(q.User.As("User").Phone.Eq(*req.UserPhone))
}
if req.InnerNo != nil {
do = do.Where(q.Trade.InnerNo.Eq(*req.InnerNo))
}
if req.OuterNo != nil {
do = do.Where(q.Trade.OuterNo.Eq(*req.OuterNo))
}
if req.Method != nil {
do = do.Where(q.Trade.Method.Eq(*req.Method))
}
if req.Platform != nil {
do = do.Where(q.Trade.Platform.Eq(*req.Platform))
}
if req.Status != nil {
do = do.Where(q.Trade.Status.Eq(*req.Status))
}
if req.CreatedAtStart != nil {
time := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.Trade.CreatedAt.Gte(time))
}
if req.CreatedAtEnd != nil {
time := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.Trade.CreatedAt.Lte(time))
}
// 查询用户列表
list, total, err := q.Trade.
Joins(q.Trade.User).
Select(
q.Trade.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
).
Where(do).
Order(q.Trade.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageTradeByAdminReq struct {
core.PageReq
UserPhone *string `json:"user_phone,omitempty"`
InnerNo *string `json:"inner_no,omitempty"`
OuterNo *string `json:"outer_no,omitempty"`
Method *int `json:"method,omitempty"`
Platform *int `json:"platform,omitempty"`
Status *int `json:"status,omitempty"`
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
}
// PageTradeOfUserByAdmin 分页查询指定用户的订单
func PageTradeOfUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeReadOfUser)
if err != nil {
return err
}
// 解析请求参数
req := new(PageTradeOfUserByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构建查询语句
do := q.Trade.Where(q.Trade.UserID.Eq(req.UserID))
if req.InnerNo != nil {
do = do.Where(q.Trade.InnerNo.Eq(*req.InnerNo))
}
if req.OuterNo != nil {
do = do.Where(q.Trade.OuterNo.Eq(*req.OuterNo))
}
if req.Method != nil {
do = do.Where(q.Trade.Method.Eq(*req.Method))
}
if req.Platform != nil {
do = do.Where(q.Trade.Platform.Eq(*req.Platform))
}
if req.Status != nil {
do = do.Where(q.Trade.Status.Eq(*req.Status))
}
if req.CreatedAtStart != nil {
time := u.DateHead(*req.CreatedAtStart)
do = do.Where(q.Trade.CreatedAt.Gte(time))
}
if req.CreatedAtEnd != nil {
time := u.DateTail(*req.CreatedAtEnd)
do = do.Where(q.Trade.CreatedAt.Lte(time))
}
// 查询订单列表
list, total, err := q.Trade.
Joins(q.Trade.User).
Select(
q.Trade.ALL,
q.User.As("User").Phone.As("User__phone"),
q.User.As("User").Name.As("User__name"),
).
Where(do).
Order(q.Trade.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
List: list,
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
})
}
type PageTradeOfUserByAdminReq struct {
core.PageReq
UserID int32 `json:"user_id" validate:"required"`
InnerNo *string `json:"inner_no,omitempty"`
OuterNo *string `json:"outer_no,omitempty"`
Method *int `json:"method,omitempty"`
Platform *int `json:"platform,omitempty"`
Status *int `json:"status,omitempty"`
CreatedAtStart *time.Time `json:"created_at_start,omitempty"`
CreatedAtEnd *time.Time `json:"created_at_end,omitempty"`
}
// ============================================================
// 创建订单
func TradeCreate(c *fiber.Ctx) error {
// 检查权限
authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 解析请求参数
req := new(TradeCreateReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
switch req.Type {
case m.TradeTypePurchase:
if req.Resource == nil {
return core.NewBizErr("购买信息不能为空")
}
case m.TradeTypeRecharge:
if req.Recharge == nil {
return core.NewBizErr("充值信息不能为空")
}
}
// 处理订单
var result *s.CreateTradeResult
switch req.Type {
case m.TradeTypePurchase:
result, err = s.Trade.Create(authCtx.User, req.CreateTradeData, req.Resource)
case m.TradeTypeRecharge:
result, err = s.Trade.Create(authCtx.User, req.CreateTradeData, req.Recharge)
}
if err != nil {
return core.NewServErr("处理购买产品信息失败", err)
}
return c.JSON(result)
}
type TradeCreateReq struct {
*s.CreateTradeData
Type m.TradeType `json:"type" validate:"required"`
Resource *s.CreateResourceData `json:"resource,omitempty"`
Recharge *s.UpdateBalanceData `json:"recharge,omitempty"`
}
// ============================================================
// 完成订单
func TradeComplete(c *fiber.Ctx) error {
// 检查权限
authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 解析请求参数
var req s.TradeRef
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 检查订单状态
err = s.Trade.CompleteTrade(authCtx.User, &req)
if err != nil {
return err
}
return c.SendStatus(fiber.StatusNoContent)
}
// 管理员完成订单
func TradeCompleteByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeTradeWriteComplete)
if err != nil {
return err
}
// 解析请求参数
var req struct {
s.TradeRef
UserID int32 `json:"user_id" validate:"required"`
}
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 获取用户信息
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
// 完成订单
err = s.Trade.CompleteTrade(user, &req.TradeRef)
if err != nil {
return err
}
return c.SendStatus(fiber.StatusNoContent)
}
// ============================================================
// 取消订单
func TradeCancel(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 解析请求参数
req := new(TradeCancelReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 取消交易
err = s.Trade.CancelTrade(&req.TradeRef)
if err != nil {
slog.Error("取消交易失败", "trade_no", req.TradeNo, "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "取消交易失败"})
}
return c.SendStatus(fiber.StatusNoContent)
}
type TradeCancelReq struct {
s.TradeRef
}
// ============================================================
// 检查订单
func TradeCheck(c *fiber.Ctx) error {
// 检查权限sse 接口暂时不检查权限
// 解析请求参数
req := new(TradeCheckReq)
if err := g.Validator.ParseQuery(c, req); err != nil {
return err
}
c.Set(fiber.HeaderContentType, "text/event-stream")
c.Set(fiber.HeaderCacheControl, "no-cache")
c.Set(fiber.HeaderConnection, "keep-alive")
c.Set(fiber.HeaderTransferEncoding, "chunked")
c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
expire := env.TradeExpire
interval := 5
for range expire / interval {
// 检查订单状态
result, err := s.Trade.CheckTrade(&req.TradeRef)
if err != nil {
slog.Error("检查订单状态失败", "trade_no", req.TradeNo, "error", err)
return
}
// 写入订单状态
_, err = fmt.Fprintf(w, "data: %d\n\n", result.Status)
if err != nil {
slog.Error("写入订单状态失败", "trade_no", req.TradeNo, "error", err)
return
}
err = w.Flush()
if err != nil {
return
}
// 当订单离开支付状态后结束查询
if result.Status != m.TradeStatusPending {
return
}
time.Sleep(time.Duration(interval) * time.Second)
}
}))
return nil
}
type TradeCheckReq struct {
s.TradeRef
}