package service import ( "context" "errors" "strings" "affiliate_dash/internal/model" ) func (s *DeliveryService) Bind(orderNo, gameAccount string, auth DeliveryLinkAuth) (*DeliveryBindResult, error) { return s.bindWithOrder(orderNo, gameAccount, &auth, 0) } func (s *DeliveryService) BindForMerchant(merchantID uint, orderNo, gameAccount string) (*DeliveryBindResult, error) { 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 } providers, _ := s.router.ResolveProviders(order) if len(providers) == 0 { return nil, errors.New("未配置发货渠道") } p := providers[0] res, err := p.CheckBind(context.Background(), order, bindUUID) if err != nil { return nil, err } return &DeliveryBindResult{ BindUUID: res.BindUUID, BindURL: res.BindURL, QRURL: res.QRURL, Bound: res.Bound, GameAccount: res.GameAccount, RoleName: res.RoleName, GameChannel: res.GameChannel, ExpectedGameAccount: res.ExpectedGameAccount, Mismatch: res.Mismatch, Message: res.Message, }, nil } // GetBindResultLinked 为公开发货链接场景提供查询绑定结果的能力。 func (s *DeliveryService) GetBindResultLinked(orderNo, bindUUID string, auth DeliveryLinkAuth) (*DeliveryBindResult, error) { bindUUID = strings.TrimSpace(bindUUID) if bindUUID == "" { return nil, errors.New("bind_uuid 不能为空") } openOrder, order, goodID, err := s.loadLinkedOrder(orderNo, &auth) if err != nil { return nil, err } if _, _, _, err := s.buildDeliveryState(openOrder, order, goodID, false); err != nil { return nil, err } providers, _ := s.router.ResolveProviders(order) if len(providers) == 0 { return nil, errors.New("未配置发货渠道") } p := providers[0] res, err := p.CheckBind(context.Background(), order, bindUUID) if err != nil { return nil, err } return &DeliveryBindResult{ BindUUID: res.BindUUID, BindURL: res.BindURL, QRURL: res.QRURL, Bound: res.Bound, GameAccount: res.GameAccount, RoleName: res.RoleName, GameChannel: res.GameChannel, ExpectedGameAccount: res.ExpectedGameAccount, Mismatch: res.Mismatch, Message: res.Message, }, nil } func (s *DeliveryService) bindWithOrder(orderNo, gameAccount string, auth *DeliveryLinkAuth, merchantID uint) (*DeliveryBindResult, error) { gameAccount = strings.TrimSpace(gameAccount) if gameAccount == "" { return nil, errors.New("请输入玩家编号") } var ( openOrder *OpenOrderQuery order *model.FulfillmentOrder goodID string err error ) if merchantID > 0 { openOrder, order, goodID, err = s.loadMerchantOrder(merchantID, orderNo) } else { openOrder, order, goodID, err = s.loadLinkedOrder(orderNo, auth) } if err != nil { return nil, err } _, _, _, err = s.buildDeliveryState(openOrder, order, goodID, true) if err != nil { return nil, err } providers, _ := s.router.ResolveProviders(order) if len(providers) == 0 { return nil, errors.New("未配置发货渠道") } var lastErr error var bindRes *ProviderBindResult var usedProvider FulfillmentProvider tried := map[string]bool{} for _, p := range providers { tried[p.Name()] = true res, err := p.Bind(context.Background(), order, gameAccount) if err == nil && res != nil { bindRes = res usedProvider = p break } lastErr = err } // Prepare 可能因 GET 成功先锁住乐玩;随后写接口凭据/IP 校验失败时,清除锁定并尝试其余渠道(如 DLC)。 if bindRes == nil { lockedName := resultDataString(order.ResultData, "provider") if lockedName != "" || lastErr != nil { s.clearSelectedProvider(order) fallbackProviders, _ := s.router.ResolveProviders(order) for _, p := range fallbackProviders { if tried[p.Name()] { continue } tried[p.Name()] = true res, err := p.Bind(context.Background(), order, gameAccount) if err == nil && res != nil { bindRes = res usedProvider = p break } lastErr = err } } } if bindRes == nil { if lastErr != nil { return nil, lastErr } return nil, errors.New("发货渠道未就绪") } // 记录选定渠道与上游单号(如 lewan_order_id),不提前写入 submit claimed stage if usedProvider != nil { patch := map[string]interface{}{ "provider": usedProvider.Name(), } for k, v := range bindRes.PatchResultData { patch[k] = v } _ = s.persistOrderResultPatch(order, patch) } return &DeliveryBindResult{ BindUUID: bindRes.BindUUID, BindURL: bindRes.BindURL, QRURL: bindRes.QRURL, Bound: bindRes.Bound, GameAccount: bindRes.GameAccount, RoleName: bindRes.RoleName, GameChannel: bindRes.GameChannel, ExpectedGameAccount: bindRes.ExpectedGameAccount, Mismatch: bindRes.Mismatch, Message: bindRes.Message, }, nil } func (s *DeliveryService) bindGood(gameAccount, goodID string) (*DeliveryBindResult, error) { dlc, ok := s.router.GetProvider(ProviderDLC).(*DLCProvider) if !ok || dlc == nil { return nil, errors.New("DLC 渠道未就绪") } res, err := dlc.Bind(context.Background(), &model.FulfillmentOrder{ ProductSKU: "", }, gameAccount) if err != nil { return nil, err } return &DeliveryBindResult{ BindUUID: res.BindUUID, BindURL: res.BindURL, QRURL: res.QRURL, Bound: res.Bound, }, nil }