From dbdadd1b10624c3a84b2f142201a73950256463d Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 4 Jul 2026 10:28:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(order):=20=E4=B8=8B=E5=8D=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=98=BE=E5=BC=8F=20client=5Ftype=20=E7=AB=AF?= =?UTF-8?q?=E5=9E=8B=EF=BC=88=E5=A5=91=E7=BA=A6=20v1.1.0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolveIsMobile:mobile/pc 显式优先,空值回退 isMobileUA(请求 UA),非法 400; 修复业务方服务端下单被 Go client UA 误判为 PC、手机用户拿不到 wap 拉起页 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o --- internal/handler/order.go | 32 ++++++++++++++++---- internal/handler/order_client_type_test.go | 34 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 internal/handler/order_client_type_test.go diff --git a/internal/handler/order.go b/internal/handler/order.go index 55074ed..c65be88 100644 --- a/internal/handler/order.go +++ b/internal/handler/order.go @@ -32,6 +32,22 @@ func isMobileUA(ua string) bool { return false } +// resolveIsMobile 决定收银台端型:业务方显式 client_type 优先(服务端间调用时 +// 下单请求 UA 是业务后端的,不代表终端用户,如 jiu 后端 Go client 恒为 PC UA); +// 缺省回退按下单请求 UA 判断(兼容旧接入方 / 浏览器直连 /paytest)。契约 v1.1.0。 +func resolveIsMobile(clientType, ua string) (bool, error) { + switch clientType { + case "mobile": + return true, nil + case "pc": + return false, nil + case "": + return isMobileUA(ua), nil + default: + return false, errors.New("client_type 仅支持 pc / mobile") + } +} + type OrderHandler struct { svc *service.OrderService } @@ -41,10 +57,11 @@ func NewOrderHandler(svc *service.OrderService) *OrderHandler { } type createOrderRequest struct { - ProductID uint64 `json:"product_id"` - BizSystem string `json:"biz_system,omitempty"` // 业务对接来源(如 jiu);带则需签名鉴权 - BizRef string `json:"biz_ref,omitempty"` // 业务引用(jiu purchase_id) - ReturnURL string `json:"return_url,omitempty"` // 自定义付款后跳回地址 + ProductID uint64 `json:"product_id"` + BizSystem string `json:"biz_system,omitempty"` // 业务对接来源(如 jiu);带则需签名鉴权 + BizRef string `json:"biz_ref,omitempty"` // 业务引用(jiu purchase_id) + ReturnURL string `json:"return_url,omitempty"` // 自定义付款后跳回地址 + ClientType string `json:"client_type,omitempty"` // 显式端类型 pc/mobile(v1.1.0);缺省按下单请求 UA 兜底 } // verifyBizSign 校验业务方下单请求的 HMAC 签名(防外部乱下单/伪造业务单)。 @@ -99,8 +116,13 @@ func (h *OrderHandler) Create(c *gin.Context) { return } } + isMobile, err := resolveIsMobile(req.ClientType, c.Request.UserAgent()) + if err != nil { + util.RespondError(c, http.StatusBadRequest, "bad_request", err.Error()) + return + } payURL, order, err := h.svc.Create(c.Request.Context(), req.ProductID, c.ClientIP(), - isMobileUA(c.Request.UserAgent()), + isMobile, service.BizParams{System: req.BizSystem, Ref: req.BizRef, ReturnURL: req.ReturnURL}) if err != nil { if errors.Is(err, service.ErrProductNotFound) { diff --git a/internal/handler/order_client_type_test.go b/internal/handler/order_client_type_test.go new file mode 100644 index 0000000..a7b307a --- /dev/null +++ b/internal/handler/order_client_type_test.go @@ -0,0 +1,34 @@ +package handler + +import "testing" + +// 契约 v1.1.0:client_type 显式端型优先,缺省回退 UA,非法值报错。 +func TestResolveIsMobile(t *testing.T) { + const goUA = "Go-http-client/1.1" + const iphoneUA = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) Mobile/15E148" + + cases := []struct { + name string + clientType string + ua string + want bool + wantErr bool + }{ + {"显式 mobile 覆盖服务端 UA", "mobile", goUA, true, false}, + {"显式 pc 覆盖手机 UA", "pc", iphoneUA, false, false}, + {"缺省回退:手机浏览器 UA", "", iphoneUA, true, false}, + {"缺省回退:服务端 Go client UA 判 PC", "", goUA, false, false}, + {"非法值报错", "tablet", iphoneUA, false, true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := resolveIsMobile(tc.clientType, tc.ua) + if tc.wantErr != (err != nil) { + t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr) + } + if !tc.wantErr && got != tc.want { + t.Fatalf("isMobile = %v, want %v", got, tc.want) + } + }) + } +}