| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- // 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)
- }
|