184 lines
5.0 KiB
Go
184 lines
5.0 KiB
Go
package handlers
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"mengyastore-backend/internal/auth"
|
||
"mengyastore-backend/internal/models"
|
||
"mengyastore-backend/internal/storage"
|
||
)
|
||
|
||
const qrSize = "320x320"
|
||
|
||
type OrderHandler struct {
|
||
productStore *storage.JSONStore
|
||
orderStore *storage.OrderStore
|
||
authClient *auth.SproutGateClient
|
||
}
|
||
|
||
type checkoutPayload struct {
|
||
ProductID string `json:"productId"`
|
||
Quantity int `json:"quantity"`
|
||
}
|
||
|
||
func NewOrderHandler(productStore *storage.JSONStore, orderStore *storage.OrderStore, authClient *auth.SproutGateClient) *OrderHandler {
|
||
return &OrderHandler{productStore: productStore, orderStore: orderStore, authClient: authClient}
|
||
}
|
||
|
||
func (h *OrderHandler) tryExtractUser(c *gin.Context) (string, string) {
|
||
authHeader := c.GetHeader("Authorization")
|
||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||
log.Println("[Order] 无 Authorization header,匿名下单")
|
||
return "", ""
|
||
}
|
||
userToken := strings.TrimPrefix(authHeader, "Bearer ")
|
||
log.Printf("[Order] 检测到用户 token,正在验证 (长度=%d)", len(userToken))
|
||
|
||
result, err := h.authClient.VerifyToken(userToken)
|
||
if err != nil {
|
||
log.Printf("[Order] 验证 token 失败: %v", err)
|
||
return "", ""
|
||
}
|
||
if !result.Valid {
|
||
log.Println("[Order] token 验证返回 valid=false")
|
||
return "", ""
|
||
}
|
||
if result.User == nil {
|
||
log.Println("[Order] token 验证成功但 user 为空")
|
||
return "", ""
|
||
}
|
||
|
||
log.Printf("[Order] 用户身份验证成功: account=%s username=%s", result.User.Account, result.User.Username)
|
||
return result.User.Account, result.User.Username
|
||
}
|
||
|
||
func (h *OrderHandler) CreateOrder(c *gin.Context) {
|
||
userAccount, userName := h.tryExtractUser(c)
|
||
|
||
var payload checkoutPayload
|
||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
||
return
|
||
}
|
||
|
||
payload.ProductID = strings.TrimSpace(payload.ProductID)
|
||
if payload.ProductID == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing required fields"})
|
||
return
|
||
}
|
||
if payload.Quantity <= 0 {
|
||
payload.Quantity = 1
|
||
}
|
||
|
||
product, err := h.productStore.GetByID(payload.ProductID)
|
||
if err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
if !product.Active {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "product is not available"})
|
||
return
|
||
}
|
||
if product.Quantity < payload.Quantity {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "库存不足"})
|
||
return
|
||
}
|
||
|
||
deliveredCodes, ok := extractCodes(&product, payload.Quantity)
|
||
if !ok {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "卡密不足"})
|
||
return
|
||
}
|
||
product.Quantity = len(product.Codes)
|
||
updatedProduct, err := h.productStore.Update(product.ID, product)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
order := models.Order{
|
||
ProductID: updatedProduct.ID,
|
||
ProductName: updatedProduct.Name,
|
||
UserAccount: userAccount,
|
||
UserName: userName,
|
||
Quantity: payload.Quantity,
|
||
DeliveredCodes: deliveredCodes,
|
||
Status: "pending",
|
||
}
|
||
created, err := h.orderStore.Create(order)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
qrPayload := fmt.Sprintf("order:%s:%s", created.ID, created.ProductID)
|
||
qrURL := fmt.Sprintf("https://api.qrserver.com/v1/create-qr-code/?size=%s&data=%s", qrSize, url.QueryEscape(qrPayload))
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"data": gin.H{
|
||
"orderId": created.ID,
|
||
"qrCodeUrl": qrURL,
|
||
"productId": created.ProductID,
|
||
"productQty": created.Quantity,
|
||
"viewCount": updatedProduct.ViewCount,
|
||
"status": created.Status,
|
||
},
|
||
})
|
||
}
|
||
|
||
func (h *OrderHandler) ConfirmOrder(c *gin.Context) {
|
||
orderID := c.Param("id")
|
||
order, err := h.orderStore.Confirm(orderID)
|
||
if err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"data": gin.H{
|
||
"orderId": order.ID,
|
||
"status": order.Status,
|
||
"deliveredCodes": order.DeliveredCodes,
|
||
},
|
||
})
|
||
}
|
||
|
||
func (h *OrderHandler) ListMyOrders(c *gin.Context) {
|
||
authHeader := c.GetHeader("Authorization")
|
||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "请先登录"})
|
||
return
|
||
}
|
||
userToken := strings.TrimPrefix(authHeader, "Bearer ")
|
||
result, err := h.authClient.VerifyToken(userToken)
|
||
if err != nil || !result.Valid || result.User == nil {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"})
|
||
return
|
||
}
|
||
|
||
orders, err := h.orderStore.ListByAccount(result.User.Account)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"data": orders})
|
||
}
|
||
|
||
func extractCodes(product *models.Product, count int) ([]string, bool) {
|
||
if count <= 0 {
|
||
return nil, false
|
||
}
|
||
if len(product.Codes) < count {
|
||
return nil, false
|
||
}
|
||
delivered := make([]string, count)
|
||
copy(delivered, product.Codes[:count])
|
||
product.Codes = product.Codes[count:]
|
||
return delivered, true
|
||
}
|