优化:已交付订单保留复制/打开发货链接,去掉作废操作
- 已交付订单仍可复用未过期链接查看兑换结果,但不再生成/续期新链接 - 商户后台已交付订单仅显示复制/打开,已作废/已过期单独标识 - 发货中/已取消订单不展示链接操作,避免无效按钮
This commit is contained in:
@@ -617,7 +617,7 @@ func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo, requ
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// 订单已取消后禁止获取发货链接(含复用未过期链接的路径),与 authorizeDeliveryLink 保持一致。
|
// 已取消订单即使存在未过期链接也不再发放(买家侧授权同样拦截已取消订单)。
|
||||||
if normalizeOrderStatus(order) == model.OrderStatusCancelled {
|
if normalizeOrderStatus(order) == model.OrderStatusCancelled {
|
||||||
return nil, newDeliveryHTTPError(http.StatusBadRequest, "订单已取消,无法获取发货链接")
|
return nil, newDeliveryHTTPError(http.StatusBadRequest, "订单已取消,无法获取发货链接")
|
||||||
}
|
}
|
||||||
@@ -625,9 +625,11 @@ func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo, requ
|
|||||||
if order.DeliveryLinkRevokedAt != nil {
|
if order.DeliveryLinkRevokedAt != nil {
|
||||||
return nil, newDeliveryHTTPError(http.StatusForbidden, "发货链接已作废")
|
return nil, newDeliveryHTTPError(http.StatusForbidden, "发货链接已作废")
|
||||||
}
|
}
|
||||||
|
// 复用未过期链接:已交付/发货中订单也允许查看兑换结果或进度,仅停止发放新链接。
|
||||||
if order.DeliveryLinkExpiresAt != nil && order.DeliveryLinkExpiresAt.After(now) {
|
if order.DeliveryLinkExpiresAt != nil && order.DeliveryLinkExpiresAt.After(now) {
|
||||||
return s.buildDeliveryLinkResult(order.OrderNo, order.DeliveryLinkExpiresAt.UTC().Truncate(time.Second), requestBaseURL), nil
|
return s.buildDeliveryLinkResult(order.OrderNo, order.DeliveryLinkExpiresAt.UTC().Truncate(time.Second), requestBaseURL), nil
|
||||||
}
|
}
|
||||||
|
// 仅待发货(含可重试的发货失败)订单能生成或续期新链接。
|
||||||
canShip, reason := CanFulfill(order)
|
canShip, reason := CanFulfill(order)
|
||||||
if !canShip {
|
if !canShip {
|
||||||
if reason == "" {
|
if reason == "" {
|
||||||
|
|||||||
@@ -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) {
|
func TestDeliveryGoodIDIncludesMedalAndCoinProducts(t *testing.T) {
|
||||||
cases := map[string]string{
|
cases := map[string]string{
|
||||||
"honor_medal_x2": "682ef39ca8f40c4234c59f45",
|
"honor_medal_x2": "682ef39ca8f40c4234c59f45",
|
||||||
|
|||||||
@@ -260,9 +260,29 @@ export function OrdersTab({
|
|||||||
key: 'delivery_link',
|
key: 'delivery_link',
|
||||||
width: 160,
|
width: 160,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
|
// 非可发货状态不再展示链接作废等操作;已交付订单保留复制/打开,便于查看兑换结果。
|
||||||
if (record.order_status === 'cancelled') {
|
if (record.order_status === 'cancelled') {
|
||||||
return <Typography.Text type="secondary">订单已取消</Typography.Text>
|
return <Typography.Text type="secondary">订单已取消</Typography.Text>
|
||||||
}
|
}
|
||||||
|
if (record.order_status === 'delivering') {
|
||||||
|
return <Typography.Text type="secondary">发货中</Typography.Text>
|
||||||
|
}
|
||||||
|
if (record.order_status === 'delivered') {
|
||||||
|
if (record.delivery_link_revoked_at) {
|
||||||
|
return <Tag color="red">已作废</Tag>
|
||||||
|
}
|
||||||
|
const expired = !record.delivery_link_expires_at
|
||||||
|
|| new Date(record.delivery_link_expires_at).getTime() < Date.now()
|
||||||
|
if (expired) {
|
||||||
|
return <Typography.Text type="secondary">已过期</Typography.Text>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Space size={4} wrap>
|
||||||
|
<Button type="link" size="small" onClick={() => onCopyLink(record.order_no)}>复制</Button>
|
||||||
|
<Button type="link" size="small" onClick={() => onOpenLink(record.order_no)}>打开</Button>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
if (record.delivery_link_revoked_at) {
|
if (record.delivery_link_revoked_at) {
|
||||||
return <Button type="link" size="small" onClick={() => onRestoreLink(record.order_no)}>恢复链接</Button>
|
return <Button type="link" size="small" onClick={() => onRestoreLink(record.order_no)}>恢复链接</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user