// Package order 出口自助扫码支付模块。 // 公开接口供出口屏 kiosk 页面轮询(可用 X-Kiosk-Token 鉴权); // 调试接口仅在 System.DebugRoutes 开启时注册,用于无真实硬件时打通全链路。 package order import ( "net/http" "strings" "github.com/gin-gonic/gin" "wails-app/internal/global" "wails-app/internal/model/common/response" "wails-app/internal/modules/order/service" paymentChannel "wails-app/internal/modules/payment/channel" ) var kioskService = service.NewOrderService() // checkKioskToken 配置了 exit-kiosk.token 时校验 X-Kiosk-Token 请求头。 func checkKioskToken(c *gin.Context) bool { token := global.GVA_CONFIG.ExitKiosk.Token if token == "" || c.GetHeader("X-Kiosk-Token") == token { return true } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "出口屏令牌无效"}) return false } // KioskState 出口屏主轮询:最近出场识别 + 费用 + 订单(含节流查单与放行联动)。 func KioskState(c *gin.Context) { if !checkKioskToken(c) { return } state, err := kioskService.KioskState(c.Query("device_code")) if err != nil { response.FailWithMessage(err.Error(), c) return } response.OkWithData(state, c) } // OrderStatus 单订单状态查询(含节流查单与放行联动)。 func OrderStatus(c *gin.Context) { if !checkKioskToken(c) { return } view, err := kioskService.GetOrderStatus(c.Param("order_no")) if err != nil { response.FailWithMessage(err.Error(), c) return } response.OkWithData(view, c) } // DebugRecognize 模拟出口 LPR 识别:走真实 HandlePassage 管线 // (临时车有费用会被拒绝放行并记录通道事件,与真实相机行为一致)。 func DebugRecognize(c *gin.Context) { var req struct { DeviceCode string `json:"device_code"` // 留空自动取第一个出场设备 PlateNumber string `json:"plate_number" binding:"required"` Direction string `json:"direction"` // 留空时 inout 通道走方向推断 } if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } deviceCode := strings.TrimSpace(req.DeviceCode) if deviceCode == "" { exitDevice, err := service.FirstExitDevice() if err != nil { response.FailWithMessage(err.Error(), c) return } deviceCode = exitDevice } result, err := service.SimulateRecognition(deviceCode, req.PlateNumber, req.Direction) if err != nil { response.FailWithMessage("识别事件已按真实管线处理(预期内拒绝): "+err.Error(), c) return } response.OkWithData(result, c) } // DebugEntry 模拟入口 LPR 识别:走真实管线建停车会话与数字票。 // device_code 留空时自动取第一个绑定了进场通道的启用设备。 func DebugEntry(c *gin.Context) { var req struct { DeviceCode string `json:"device_code"` // 留空自动取第一个进场设备 PlateNumber string `json:"plate_number" binding:"required"` Hours int `json:"hours"` // 回拨入场时间小时数(如 26),保证出场时有费用 } if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } deviceCode := strings.TrimSpace(req.DeviceCode) if deviceCode == "" { entryDevice, err := service.FirstEntryDevice() if err != nil { response.FailWithMessage(err.Error(), c) return } deviceCode = entryDevice } result, err := service.SimulateRecognition(deviceCode, req.PlateNumber, "in") if err != nil { response.FailWithMessage("模拟入场失败: "+err.Error(), c) return } // 测试支付流程时回拨入场时间,跨过免费窗口 if req.Hours > 0 { if err := service.BackfillEntryTime(req.PlateNumber, req.Hours); err != nil { response.FailWithMessage("入场成功但回拨入场时间失败: "+err.Error(), c) return } } response.OkWithData(result, c) } // DebugFullFlow 一键联调:确保在场 → 回拨入场时间 → 出口识别 → mock 支付 → 查单确认开闸。 // hours 为入场时间回拨小时数(默认 26),保证费用必然产生。 func DebugFullFlow(c *gin.Context) { var req struct { DeviceCode string `json:"device_code"` PlateNumber string `json:"plate_number" binding:"required"` Hours int `json:"hours"` } if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } state, err := kioskService.DebugFullFlow(req.DeviceCode, req.PlateNumber, req.Hours) if err != nil { response.FailWithMessage(err.Error(), c) return } response.OkWithData(state, c) } // DebugMockPay mock 渠道模拟"车主已完成支付",随后查单即确认到账。 func DebugMockPay(c *gin.Context) { var req struct { OrderNo string `json:"order_no" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } if err := paymentChannel.MarkMockPaid(req.OrderNo); err != nil { response.FailWithMessage(err.Error(), c) return } response.OkWithMessage("mock 渠道已标记支付,等待查单确认", c) } // SetupExitKioskPublicRouter 出口屏轮询接口(公开组,可选令牌)。 func SetupExitKioskPublicRouter(router *gin.RouterGroup) { group := router.Group("/exit-kiosk") group.GET("/state", KioskState) group.GET("/order/:order_no", OrderStatus) } // SetupExitKioskDebugRouter 联调端点,仅 System.DebugRoutes 开启时注册。 func SetupExitKioskDebugRouter(router *gin.RouterGroup, enabled bool) { if !enabled { return } group := router.Group("/exit-kiosk/debug") group.POST("/entry", DebugEntry) group.POST("/recognize", DebugRecognize) group.POST("/mock-pay", DebugMockPay) group.POST("/full-flow", DebugFullFlow) }