diff --git a/backend/internal/handler/open_v1.go b/backend/internal/handler/open_v1.go index 43d30e8..8d83605 100644 --- a/backend/internal/handler/open_v1.go +++ b/backend/internal/handler/open_v1.go @@ -227,6 +227,22 @@ func (h *OpenV1Handler) BindDeliveryOrder(c *gin.Context) { response.OK(c, result) } +// GetDeliveryBindResult GET /api/client/v1/orders/:order_no/delivery/bind-result +// 查询绑定结果与角色信息(自建发货页轮询绑定状态用)。 +func (h *OpenV1Handler) GetDeliveryBindResult(c *gin.Context) { + openlog.SetAction(c, openlog.ActionQuery) + orderNo := c.Param("order_no") + bindUUID := c.Query("bind_uuid") + openlog.Info(c, "delivery_bind_result start order_no=%s bind_uuid=%s", orderNo, bindUUID) + result, err := h.deliverySvc.GetBindResult(middleware.GetMerchantID(c), orderNo, bindUUID) + if err != nil { + openlog.Warn(c, "delivery_bind_result fail order_no=%s err=%v", orderNo, err) + response.BadRequest(c, err.Error()) + return + } + response.OK(c, result) +} + type openDeliverySubmitReq struct { GameAccount string `json:"game_account" binding:"required"` BindUUID string `json:"bind_uuid" binding:"required"` diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 3887143..95f36c0 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -87,6 +87,7 @@ func Setup(h *Handlers) *gin.Engine { clientOpen.GET("/orders/:order_no/delivery-link", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireAPIScope("orders:read", "shipping:read"), h.Open.GetDeliveryLink) clientOpen.GET("/orders/:order_no/delivery", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireAPIScope("orders:read", "shipping:read"), h.Open.GetDeliveryOrder) clientOpen.POST("/orders/:order_no/delivery/bind", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireAPIScope("orders:write"), h.Open.BindDeliveryOrder) + clientOpen.GET("/orders/:order_no/delivery/bind-result", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireAPIScope("orders:read", "shipping:read"), h.Open.GetDeliveryBindResult) clientOpen.POST("/orders/:order_no/delivery/submit", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireAPIScope("orders:write"), h.Open.SubmitDeliveryOrder) clientOpen.GET("/wallet", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireAPIScope("wallet:read"), h.Open.GetWallet) } diff --git a/backend/internal/service/delivery.go b/backend/internal/service/delivery.go index 5227e51..2ef7ada 100644 --- a/backend/internal/service/delivery.go +++ b/backend/internal/service/delivery.go @@ -128,9 +128,14 @@ type DeliveryProduct struct { } type DeliveryBindResult struct { - BindUUID string `json:"bind_uuid"` - BindURL string `json:"bind_url"` - QRURL string `json:"qr_url"` + BindUUID string `json:"bind_uuid"` + BindURL string `json:"bind_url"` + QRURL string `json:"qr_url"` + Bound bool `json:"bound"` + GameAccount string `json:"game_account,omitempty"` + RoleName string `json:"role_name,omitempty"` + GameChannel string `json:"game_channel,omitempty"` + Message string `json:"message,omitempty"` } type DeliveryLinkResult struct { @@ -194,6 +199,37 @@ func (s *DeliveryService) BindForMerchant(merchantID uint, orderNo, gameAccount return s.bindWithOrder(orderNo, gameAccount, nil, merchantID) } +// GetBindResult 查询绑定结果(自建发货页轮询用)。 +// 绑定未完成时返回 bound=false(不报错),由前端提示继续扫码。 +func (s *DeliveryService) GetBindResult(merchantID uint, orderNo, bindUUID string) (*DeliveryBindResult, error) { + bindUUID = strings.TrimSpace(bindUUID) + if bindUUID == "" { + return nil, errors.New("bind_uuid 不能为空") + } + openOrder, order, goodID, err := s.loadMerchantOrder(merchantID, orderNo) + if err != nil { + return nil, err + } + if _, _, _, err := s.buildDeliveryState(openOrder, order, goodID, false); err != nil { + return nil, err + } + account, err := s.accountBound(bindUUID, goodID) + if err != nil { + if strings.Contains(err.Error(), "尚未绑定") || strings.Contains(err.Error(), "未绑定") { + return &DeliveryBindResult{BindUUID: bindUUID, Bound: false, Message: "尚未绑定,请先完成扫码绑定"}, nil + } + return nil, err + } + return &DeliveryBindResult{ + BindUUID: bindUUID, + Bound: true, + GameAccount: stringFromMap(account, "game_account"), + RoleName: stringFromMap(account, "game_account_role_name"), + GameChannel: gameChannelText(account), + Message: "绑定成功", + }, nil +} + func (s *DeliveryService) bindWithOrder(orderNo, gameAccount string, auth *DeliveryLinkAuth, merchantID uint) (*DeliveryBindResult, error) { gameAccount = strings.TrimSpace(gameAccount) if gameAccount == "" { diff --git a/frontend/src/openapi/endpoints.ts b/frontend/src/openapi/endpoints.ts index cb83b17..6c5f3ad 100644 --- a/frontend/src/openapi/endpoints.ts +++ b/frontend/src/openapi/endpoints.ts @@ -359,6 +359,41 @@ export const endpoints: EndpointSpec[] = [ '同一订单重复绑定时,请以最新返回的绑定凭证为准。', ], }, + { + key: 'delivery-bind-result', + group: 'delivery-self', + method: 'GET', + path: '/api/client/v1/orders/{order_no}/delivery/bind-result', + title: '查询绑定结果', + summary: '查询绑定状态与角色信息(自建发货页轮询用),未绑定时返回 bound=false', + scope: 'orders:read 或 shipping:read', + pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }], + queryParams: [{ name: 'bind_uuid', type: 'string', required: true, desc: '发起绑定时返回的绑定凭证', example: 'bind-123' }], + responseExample: `{ + "code": 0, + "message": "ok", + "data": { + "bind_uuid": "bind-123", + "bound": true, + "game_account": "4808146277", + "role_name": "巫师哈丁12", + "game_channel": "IOS-微信", + "message": "绑定成功" + } +}`, + responseFields: [ + { name: 'bind_uuid', type: 'string', desc: '绑定凭证' }, + { name: 'bound', type: 'bool', desc: '是否已完成绑定' }, + { name: 'game_account', type: 'string', desc: '玩家编号(已绑定时返回)' }, + { name: 'role_name', type: 'string', desc: '角色名(已绑定时返回)' }, + { name: 'game_channel', type: 'string', desc: '区服渠道(已绑定时返回)' }, + { name: 'message', type: 'string', desc: '状态说明' }, + ], + notes: [ + '用户扫码绑定完成前返回 bound=false,自建页可定时轮询此接口刷新状态。', + '轮询间隔建议 2-3 秒,绑定成功后展示角色信息并引导提交发货。', + ], + }, { key: 'delivery-submit', group: 'delivery-self',