// cashier_page.go submit.php 直出的自包含收银台页(不 302、不依赖前端 SPA), // 二维码由服务端生成 PNG data URL;状态经 /cashier-status 轮询。 package service import ( "bytes" "context" "encoding/base64" "html/template" "strings" "github.com/cyi-cc/fun" "github.com/skip2/go-qrcode" "github.com/cyi-cc/cyi-box/backend/internal/db" "github.com/cyi-cc/cyi-box/backend/internal/store" ) // cashierTmpl 收银台页面模板(新粗野主义风格,与前端 Cashier.vue 一致)。 var cashierTmpl = template.Must(template.New("cashier").Parse(` 收银台 · ¥{{.Money}}
订单号 {{.TradeNo}}
{{or .Subject "商品订单"}}
¥{{.Money}}
`)) type cashierPageData struct { TradeNo string Subject string Money string QRDataURL string ExpiredAt int64 Status int64 } // renderCashier 渲染收银台 HTML(二维码内容 → PNG data URL)。 func renderCashier(c *fun.RouteCtx, o db.PayOrder) error { d := cashierPageData{ TradeNo: o.TradeNo, Subject: o.Subject, Money: fenToYuan(o.Money), ExpiredAt: o.ExpiredAt, Status: o.Status, } if o.Qrcode != "" { if png, err := qrcode.Encode(o.Qrcode, qrcode.Medium, 256); err == nil { d.QRDataURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(png) } } var buf bytes.Buffer if err := cashierTmpl.Execute(&buf, d); err != nil { return err } c.RequestCtx.Response.Header.Set("Content-Type", "text/html; charset=utf-8") c.RequestCtx.SetBody(buf.Bytes()) return nil } // PayCashierStatusHandler GET /cashier-status?trade_no=:收银台页轮询 // (公开接口,trade_no 不可枚举即凭证;前端经 /api 前缀代理访问)。 func PayCashierStatusHandler(st *store.Store) fun.RouteHandler { return func(c *fun.RouteCtx) error { status, returnURL, err := cashierStatusReply(st, strings.TrimSpace(c.Param("trade_no"))) if err != nil { return epayJSON(c, map[string]any{"status": -1}) } return epayJSON(c, map[string]any{"status": status, "return_url": returnURL}) } } // cashierStatusReply 收银台状态查询:订单态 + 已支付时的签名回跳地址(RPC 与页面轮询共用)。 func cashierStatusReply(st *store.Store, tradeNo string) (int64, string, error) { o, err := st.Q().GetPayOrderByTradeNo(context.Background(), tradeNo) if err != nil { return 0, "", err } lazyCloseExpired(st, &o) returnURL := "" if o.Status == payStatusPaid && o.ReturnUrl != "" { if pid, key, err := payMerchant(st); err == nil { returnURL = buildSignedURL(o.ReturnUrl, notifyParams(o, pid), key) } } return o.Status, returnURL, nil }