diff --git a/backend/internal/service/delivery.go b/backend/internal/service/delivery.go
index 4e36ab3..cf9c5b0 100644
--- a/backend/internal/service/delivery.go
+++ b/backend/internal/service/delivery.go
@@ -617,7 +617,7 @@ func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo, requ
if err != nil {
return nil, err
}
- // 订单已取消后禁止获取发货链接(含复用未过期链接的路径),与 authorizeDeliveryLink 保持一致。
+ // 已取消订单即使存在未过期链接也不再发放(买家侧授权同样拦截已取消订单)。
if normalizeOrderStatus(order) == model.OrderStatusCancelled {
return nil, newDeliveryHTTPError(http.StatusBadRequest, "订单已取消,无法获取发货链接")
}
@@ -625,9 +625,11 @@ func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo, requ
if order.DeliveryLinkRevokedAt != nil {
return nil, newDeliveryHTTPError(http.StatusForbidden, "发货链接已作废")
}
+ // 复用未过期链接:已交付/发货中订单也允许查看兑换结果或进度,仅停止发放新链接。
if order.DeliveryLinkExpiresAt != nil && order.DeliveryLinkExpiresAt.After(now) {
return s.buildDeliveryLinkResult(order.OrderNo, order.DeliveryLinkExpiresAt.UTC().Truncate(time.Second), requestBaseURL), nil
}
+ // 仅待发货(含可重试的发货失败)订单能生成或续期新链接。
canShip, reason := CanFulfill(order)
if !canShip {
if reason == "" {
diff --git a/backend/internal/service/delivery_test.go b/backend/internal/service/delivery_test.go
index f4af162..1ea06ed 100644
--- a/backend/internal/service/delivery_test.go
+++ b/backend/internal/service/delivery_test.go
@@ -116,6 +116,69 @@ func TestDeliveryLinkRejectedAfterOrderCancelled(t *testing.T) {
}
}
+func TestDeliveryLinkAfterDeliveredReusesButNeverGenerates(t *testing.T) {
+ db := newServiceTestDB(t)
+ merchantID, product := seedFulfillmentMerchant(t, db, "delivery-done", 5000, 5, 100)
+ fulfillmentSvc := NewFulfillmentService(db, nil)
+ deliverySvc := NewDeliveryService(fulfillmentSvc, "https://bff.example", "dlc", "https://shop.example", "link-secret", 60)
+
+ created, err := fulfillmentSvc.CreateOrder(CreateFulfillmentOrderInput{
+ MerchantID: merchantID,
+ APIClientID: 1,
+ ClientOrderNo: "delivery-done-001",
+ SKU: product.SKU,
+ })
+ if err != nil {
+ t.Fatalf("create order: %v", err)
+ }
+ link, err := deliverySvc.GetOrCreateDeliveryLink(merchantID, created.Order.OrderNo, "https://shop.example")
+ if err != nil {
+ t.Fatalf("get delivery link: %v", err)
+ }
+ if _, err := fulfillmentSvc.UpdateFulfillment(FulfillmentUpdateInput{
+ MerchantID: merchantID,
+ APIClientID: 1,
+ OrderNo: created.Order.OrderNo,
+ Status: model.OrderStatusDelivered,
+ }); err != nil {
+ t.Fatalf("mark delivered: %v", err)
+ }
+ // 已交付订单仍可复用未过期的既有链接(查看兑换结果),但不会生成新链接。
+ reused, err := deliverySvc.GetOrCreateDeliveryLink(merchantID, created.Order.OrderNo, "https://shop.example")
+ if err != nil {
+ t.Fatalf("delivered order should reuse existing link: %v", err)
+ }
+ if reused.DeliveryURL != link.DeliveryURL || reused.Exp != link.Exp {
+ t.Fatalf("reused link should match original, got %+v", reused)
+ }
+ // 买家侧仍可通过既有链接只读查看交付结果。
+ if _, err := deliverySvc.GetOrder(created.Order.OrderNo, DeliveryLinkAuth{Exp: link.Exp, Sign: link.Sign}); err != nil {
+ t.Fatalf("delivered order link should stay readable for the buyer: %v", err)
+ }
+
+ // 从未生成过链接的已交付订单,不能再获取新链接。
+ second, err := fulfillmentSvc.CreateOrder(CreateFulfillmentOrderInput{
+ MerchantID: merchantID,
+ APIClientID: 1,
+ ClientOrderNo: "delivery-done-002",
+ SKU: product.SKU,
+ })
+ if err != nil {
+ t.Fatalf("create second order: %v", err)
+ }
+ if _, err := fulfillmentSvc.UpdateFulfillment(FulfillmentUpdateInput{
+ MerchantID: merchantID,
+ APIClientID: 1,
+ OrderNo: second.Order.OrderNo,
+ Status: model.OrderStatusDelivered,
+ }); err != nil {
+ t.Fatalf("mark second delivered: %v", err)
+ }
+ if _, err := deliverySvc.GetOrCreateDeliveryLink(merchantID, second.Order.OrderNo, "https://shop.example"); err == nil {
+ t.Fatalf("delivered order without existing link should not generate a new one")
+ }
+}
+
func TestDeliveryGoodIDIncludesMedalAndCoinProducts(t *testing.T) {
cases := map[string]string{
"honor_medal_x2": "682ef39ca8f40c4234c59f45",
diff --git a/frontend/src/pages/merchantCenterTabs.tsx b/frontend/src/pages/merchantCenterTabs.tsx
index 4e67445..32fad9e 100644
--- a/frontend/src/pages/merchantCenterTabs.tsx
+++ b/frontend/src/pages/merchantCenterTabs.tsx
@@ -260,9 +260,29 @@ export function OrdersTab({
key: 'delivery_link',
width: 160,
render: (_, record) => {
+ // 非可发货状态不再展示链接作废等操作;已交付订单保留复制/打开,便于查看兑换结果。
if (record.order_status === 'cancelled') {
return 订单已取消
}
+ if (record.order_status === 'delivering') {
+ return 发货中
+ }
+ if (record.order_status === 'delivered') {
+ if (record.delivery_link_revoked_at) {
+ return 已作废
+ }
+ const expired = !record.delivery_link_expires_at
+ || new Date(record.delivery_link_expires_at).getTime() < Date.now()
+ if (expired) {
+ return 已过期
+ }
+ return (
+
+
+
+
+ )
+ }
if (record.delivery_link_revoked_at) {
return
}