Files
affiliate_dash/backend/internal/service/delivery_bind.go
T
yml2213 24fa6a97a0 refactor(delivery): 按职责拆分 delivery.go(1162 → 最大 249 行)
纯文件级移动,无行为变更:
- delivery.go            常量、DeliveryService 结构、构造与依赖注入
- delivery_types.go      对外 DTO 与 deliveryHTTPError
- delivery_provider.go   渠道取消/同步与 result_data 维护
- delivery_query.go      订单查询与发货状态构建
- delivery_bind.go       绑定与绑定结果查询
- delivery_submit.go     提交发货与提交阶段状态机
- deliveryGoodID 并入 delivery_helpers.go,链接管理并入 delivery_link.go

校验:拆分前后 55 个 func/type/const 声明完全一致(无缺失、无多出、无重复)。
2026-10-06 19:43:05 +08:00

208 lines
6.0 KiB
Go

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
}