Files
OpenFlare/internal/apps/order/routers.go
T
2026-06-08 20:34:27 +08:00

220 lines
9.7 KiB
Go

/*
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package order
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/linux-do/credit/internal/apps/oauth"
"github.com/linux-do/credit/internal/db"
"github.com/linux-do/credit/internal/model"
"github.com/linux-do/credit/internal/util"
)
type TransactionListRequest struct {
Page int `json:"page" form:"page" binding:"min=1"`
PageSize int `json:"page_size" form:"page_size" binding:"min=1,max=100"`
Types []string `json:"types" form:"types" binding:"omitempty,dive,oneof=receive payment transfer community online test distribute red_envelope_send red_envelope_receive red_envelope_refund"`
Statuses []string `json:"statuses" form:"statuses" binding:"omitempty,dive,oneof=success pending failed expired disputing refund refused"`
ClientID string `json:"client_id" form:"client_id" binding:"omitempty"`
StartTime *time.Time `json:"startTime" form:"startTime" binding:"omitempty"`
EndTime *time.Time `json:"endTime" form:"endTime" binding:"omitempty,gtfield=StartTime"`
ID *uint64 `json:"id,string" form:"id" binding:"omitempty"`
OrderName string `json:"order_name" form:"order_name" binding:"omitempty"`
PayerUsername string `json:"payer_username" form:"payer_username" binding:"omitempty"`
PayeeUsername string `json:"payee_username" form:"payee_username" binding:"omitempty"`
PayeeTransferStatus model.OrderTransferStatus `json:"payee_transfer_status" form:"payee_transfer_status" binding:"omitempty,oneof=pending completed"`
}
type TransactionListResponse struct {
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Orders []struct {
model.Order
AppName string `json:"app_name"`
AppHomepageURL string `json:"app_homepage_url"`
AppDescription string `json:"app_description"`
RedirectURI string `json:"redirect_uri"`
DisputeID *uint64 `json:"dispute_id,string"`
PayerUsername string `json:"payer_username"`
PayeeUsername string `json:"payee_username"`
PayerAvatarURL string `json:"payer_avatar_url"`
PayeeAvatarURL string `json:"payee_avatar_url"`
PayeeTransferStatus string `json:"payee_transfer_status"`
PayeeTransferAt string `json:"payee_transfer_at"`
} `json:"orders"`
}
// ListTransactions 获取交易列表
// @Tags order
// @Accept json
// @Produce json
// @Param request body TransactionListRequest false "request body"
// @Success 200 {object} util.ResponseAny
// @Router /api/v1/order/transactions [post]
func ListTransactions(c *gin.Context) {
var req TransactionListRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, util.Err(err.Error()))
return
}
user, _ := util.GetFromContext[*model.User](c, oauth.UserObjKey)
baseQuery := db.DB(c.Request.Context()).Model(&model.Order{}).
Select("orders.*, merchant_api_keys.app_name, merchant_api_keys.app_homepage_url, merchant_api_keys.app_description, merchant_api_keys.redirect_uri, disputes.id as dispute_id, payer_user.username as payer_username, payee_user.username as payee_username, payer_user.avatar_url as payer_avatar_url, payee_user.avatar_url as payee_avatar_url, order_transfers.status as payee_transfer_status, order_transfers.transfer_at as payee_transfer_at").
Joins("LEFT JOIN merchant_api_keys ON orders.client_id = merchant_api_keys.client_id").
Joins("LEFT JOIN disputes ON orders.id = disputes.order_id").
Joins("LEFT JOIN users as payer_user ON orders.payer_user_id = payer_user.id").
Joins("LEFT JOIN users as payee_user ON orders.payee_user_id = payee_user.id").
Joins("LEFT JOIN order_transfers ON orders.id = order_transfers.order_id")
clientIDHandled := false
if len(req.Types) > 0 {
// 使用 IN 查询支持多个类型
var conditions []string
var args []interface{}
for _, t := range req.Types {
orderType := model.OrderType(t)
switch orderType {
case model.OrderTypeReceive:
// receive 类型:查询当前用户作为收款方的 payment 订单
conditions = append(conditions, "(orders.type = ? AND orders.payee_user_id = ?)")
args = append(args, model.OrderTypePayment, user.ID)
case model.OrderTypeCommunity, model.OrderTypeRedEnvelopeRefund, model.OrderTypeRedEnvelopeReceive:
// community、red_envelope_refund、red_envelope_receive 类型:查询当前用户作为收款方的订单
conditions = append(conditions, "(orders.type = ? AND orders.payee_user_id = ?)")
args = append(args, orderType, user.ID)
case model.OrderTypeOnline:
// online 类型:商家可查看自己 client_id 的所有订单,普通用户只能查看与自己相关的订单
if req.ClientID != "" {
clientIDHandled = true
var count int64
if err := db.DB(c.Request.Context()).Model(&model.MerchantAPIKey{}).
Where("client_id = ? AND user_id = ?", req.ClientID, user.ID).
Count(&count).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
if count > 0 {
conditions = append(conditions, "(orders.type = ? AND orders.client_id = ?)")
args = append(args, orderType, req.ClientID)
} else {
conditions = append(conditions, "(orders.type = ? AND orders.client_id = ? AND (orders.payer_user_id = ? OR orders.payee_user_id = ?))")
args = append(args, orderType, req.ClientID, user.ID, user.ID)
}
} else {
conditions = append(conditions, "(orders.type = ? AND (orders.payer_user_id = ? OR orders.payee_user_id = ?))")
args = append(args, orderType, user.ID, user.ID)
}
case model.OrderTypePayment, model.OrderTypeTransfer, model.OrderTypeTest, model.OrderTypeRedEnvelopeSend:
// payment、transfer、test、red_envelope_send 类型:查询当前用户作为付款方的订单
conditions = append(conditions, "(orders.type = ? AND orders.payer_user_id = ?)")
args = append(args, orderType, user.ID)
case model.OrderTypeDistribute:
// distribute 类型:查询当前用户作为付款方或者收款方的订单
conditions = append(conditions, "((orders.type = ? AND orders.payer_user_id = ?) OR (orders.type = ? AND orders.payee_user_id = ?))")
args = append(args, orderType, user.ID, orderType, user.ID)
}
}
if len(conditions) > 0 {
combinedCondition := conditions[0]
for i := 1; i < len(conditions); i++ {
combinedCondition += " OR " + conditions[i]
}
baseQuery = baseQuery.Where(combinedCondition, args...)
}
} else {
// 查询所有与当前用户相关的订单,但排除用户作为payer的red_envelope_receive订单
baseQuery = baseQuery.Where(
"orders.payee_user_id = ? OR (orders.payer_user_id = ? AND orders.type != ?)",
user.ID, user.ID, model.OrderTypeRedEnvelopeReceive,
)
}
if len(req.Statuses) > 0 {
baseQuery = baseQuery.Where("orders.status IN ?", req.Statuses)
}
if req.ClientID != "" && !clientIDHandled {
baseQuery = baseQuery.Where("orders.client_id = ?", req.ClientID)
}
if req.ID != nil {
baseQuery = baseQuery.Where("orders.id = ?", req.ID)
}
if req.OrderName != "" {
baseQuery = baseQuery.Where("orders.order_name LIKE ?", req.OrderName+"%")
}
if req.PayerUsername != "" {
baseQuery = baseQuery.Where("payer_user.username LIKE ?", req.PayerUsername+"%")
}
if req.PayeeUsername != "" {
baseQuery = baseQuery.Where("payee_user.username LIKE ?", req.PayeeUsername+"%")
}
if req.PayeeTransferStatus != "" {
switch req.PayeeTransferStatus {
case model.OrderTransferStatusPending:
baseQuery = baseQuery.Where("order_transfers.status = ?", model.OrderTransferStatusPending)
case model.OrderTransferStatusCompleted:
baseQuery = baseQuery.Where("order_transfers.status = ? OR order_transfers.status IS NULL", model.OrderTransferStatusCompleted)
}
}
if req.StartTime != nil {
baseQuery = baseQuery.Where("orders.created_at >= ?", req.StartTime)
}
if req.EndTime != nil {
baseQuery = baseQuery.Where("orders.created_at <= ?", req.EndTime)
}
var total int64
if err := baseQuery.Count(&total).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
response := &TransactionListResponse{
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}
offset := (req.Page - 1) * req.PageSize
if err := baseQuery.Order("orders.created_at DESC").Offset(offset).Limit(req.PageSize).Find(&response.Orders).Error; err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
// 转换订单类型:从收款方视角看,payment 订单应该显示为 receive
// 并更新 Payee_transfer_status,兼容为空的场景
for i := range response.Orders {
if response.Orders[i].PayeeTransferStatus == "" {
response.Orders[i].PayeeTransferStatus = string(model.OrderTransferStatusCompleted)
}
if response.Orders[i].Type == model.OrderTypePayment && response.Orders[i].PayeeUserID == user.ID {
response.Orders[i].Type = model.OrderTypeReceive
}
}
c.JSON(http.StatusOK, util.OK(response))
}