diff --git a/backend/internal/service/provider_dlc.go b/backend/internal/service/provider_dlc.go index 2c762c8..399b718 100644 --- a/backend/internal/service/provider_dlc.go +++ b/backend/internal/service/provider_dlc.go @@ -220,9 +220,10 @@ func (p *DLCProvider) Submit(ctx context.Context, order *model.FulfillmentOrder, partial := map[string]interface{}{ "provider": ProviderDLC, } - effectiveOrderSN := order.OrderNo - if savedSN := resultDataString(order.ResultData, "order_sn"); savedSN != "" { - effectiveOrderSN = savedSN + // 代练仓下正式单前会按这个单号回查。随机后缀在库里不存在,不能再拿去下单。 + effectiveOrderSN := strings.TrimSpace(order.OrderNo) + if savedSN := resultDataString(order.ResultData, "order_sn"); savedSN != "" && savedSN != effectiveOrderSN { + queueOrderID = "" } if queueOrderID == "" { @@ -263,29 +264,7 @@ func (p *DLCProvider) Submit(ctx context.Context, order *model.FulfillmentOrder, upstreamOrder, err := p.createUpstreamOrder(queueOrderID, gameAccount, goodID, effectiveOrderSN) if err != nil { - if isQueueDeadError(err) { - // 创建正式单阶段出现订单不可操作或重复提交,尝试换新重试单号重建队列重试一次 - delete(partial, "queue_order_id") - retryBaseSN := fmt.Sprintf("%s_%x", strings.SplitN(order.OrderNo, "_", 2)[0], time.Now().UnixNano()%0xffff) - newQueueID, newSN, createErr := p.createOrderQueue(goodID, retryBaseSN, goodsNo) - if createErr == nil { - queueOrderID = newQueueID - effectiveOrderSN = newSN - partial["queue_order_id"] = queueOrderID - partial["order_sn"] = effectiveOrderSN - if patchErr := p.patchOrderQueue(queueOrderID, gameAccount, bindUUID, goodsNo, goodsName); patchErr == nil { - if retriedOrder, retriedErr := p.createUpstreamOrder(queueOrderID, gameAccount, goodID, effectiveOrderSN); retriedErr == nil { - upstreamOrder = retriedOrder - err = nil - } else { - err = retriedErr - } - } - } - } - if err != nil { - return &ProviderSubmitResult{PatchResultData: partial}, fmt.Errorf("创建上游正式订单失败: %w", err) - } + return &ProviderSubmitResult{PatchResultData: partial}, fmt.Errorf("创建上游正式订单失败: %w", err) } providerOrderNo := firstNonEmpty( @@ -398,43 +377,28 @@ func (p *DLCProvider) createOrderQueue(goodID, orderNo, goodsNo string) (string, var out struct { Orders []map[string]interface{} `json:"orders"` } - usedSN := orderNo + orderNo = strings.TrimSpace(orderNo) payload := map[string]interface{}{ "good_id": goodID, "quantity": 1, - "order_sn": usedSN, + "order_sn": orderNo, "h5_prefix": p.channel, } if goodsNo != "" { payload["goods_no"] = goodsNo } - err := p.signProxy("/public/users/orders-queue", "POST", payload, &out) - if err != nil { - if strings.Contains(err.Error(), "正在处理中") || - strings.Contains(err.Error(), "请勿重复提交") || - strings.Contains(err.Error(), "已有发货记录") || - strings.Contains(err.Error(), "已处理") { - // DLC 已存在此 order_sn 的队列/发货记录,追加随机后缀重新创建队列 - retrySN := fmt.Sprintf("%s_%x", strings.SplitN(orderNo, "_", 2)[0], time.Now().UnixNano()%0xffff) - usedSN = retrySN - payload["order_sn"] = usedSN - out.Orders = nil - if retryErr := p.signProxy("/public/users/orders-queue", "POST", payload, &out); retryErr == nil && len(out.Orders) > 0 { - err = nil - } - } - } - if err != nil { - return "", "", err + // 代练仓已有这条单号时,原文是「请续发未完成项或重试失败项」。换一个带后缀的单号会让回查 404。 + if err := p.signProxy("/public/users/orders-queue", "POST", payload, &out); err != nil { + return "", orderNo, err } if len(out.Orders) == 0 { - return "", "", errors.New("发货服务未返回队列订单") + return "", orderNo, errors.New("发货服务未返回队列订单") } orderID := firstNonEmpty(stringFromMap(out.Orders[0], "_id"), stringFromMap(out.Orders[0], "id")) if orderID == "" { - return "", "", errors.New("发货服务未返回队列订单 ID") + return "", orderNo, errors.New("发货服务未返回队列订单 ID") } - return orderID, usedSN, nil + return orderID, orderNo, nil } func (p *DLCProvider) patchOrderQueue(orderID, gameAccount, bindUUID, goodsNo, goodsName string) error { @@ -632,4 +596,3 @@ func (p *DLCProvider) signProxy(path, method string, data interface{}, out inter return lastErr } - diff --git a/backend/internal/service/provider_dlc_test.go b/backend/internal/service/provider_dlc_test.go index b9bfc1f..71ba537 100644 --- a/backend/internal/service/provider_dlc_test.go +++ b/backend/internal/service/provider_dlc_test.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "net/http/httptest" + "strings" "sync/atomic" "testing" "time" @@ -179,6 +180,39 @@ func TestSignProxyDoesNotRetryOnRateLimit(t *testing.T) { } } +func TestDLCCreateOrderQueueKeepsOriginalOrderSN(t *testing.T) { + const orderNo = "FO20261006180010b19fbe66ec3" + var requestedSN string + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body struct { + Path string `json:"path"` + Data map[string]interface{} `json:"data"` + } + _ = json.NewDecoder(r.Body).Decode(&body) + requestedSN, _ = body.Data["order_sn"].(string) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "code": 1, + "message": "该订单已有发货记录,请续发未完成项或重试失败项", + }) + })) + defer server.Close() + + p := NewDLCProvider(server.URL, "dlc", &http.Client{Timeout: 5 * time.Second}) + _, sn, err := p.createOrderQueue("good-1", orderNo, "suit_romantic_destiny") + if err == nil { + t.Fatal("已有发货记录时应返回代练仓原文,不能换成新单号重试") + } + if !strings.Contains(err.Error(), "请续发未完成项或重试失败项") { + t.Fatalf("应保留代练仓原文,实际: %v", err) + } + if requestedSN != orderNo || sn != orderNo || strings.Contains(requestedSN, "_") { + t.Fatalf("请求单号被改写: requested=%s returned=%s", requestedSN, sn) + } +} + func TestSignProxyDoesNotRetryOnFormalOrderCreation(t *testing.T) { var callCount int32 @@ -211,5 +245,3 @@ func TestSignProxyDoesNotRetryOnFormalOrderCreation(t *testing.T) { t.Fatalf("正式下单接口预期只调用 1 次且不触发重试,实际调用: %d", finalCount) } } - -