feat(server/pay): 订单展示层过期态(created 超 TTL 显 expired,零迁移)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,9 @@ type orderView struct {
|
|||||||
PaidAt *string `json:"paid_at,omitempty"`
|
PaidAt *string `json:"paid_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未付订单展示层过期窗口(近似 pay 会话时效;仅影响列表/详情显示,不改台账)。
|
||||||
|
const orderPendingTTL = 30 * time.Minute
|
||||||
|
|
||||||
func orderViewFromRow(row *PurchaseRow) orderView {
|
func orderViewFromRow(row *PurchaseRow) orderView {
|
||||||
v := orderView{
|
v := orderView{
|
||||||
OrderNo: row.OutTradeNo, SKU: row.SKU, Method: row.Method, Status: row.Status,
|
OrderNo: row.OutTradeNo, SKU: row.SKU, Method: row.Method, Status: row.Status,
|
||||||
@@ -178,6 +181,9 @@ func orderViewFromRow(row *PurchaseRow) orderView {
|
|||||||
s := row.PaidAt.Time.UTC().Format(time.RFC3339)
|
s := row.PaidAt.Time.UTC().Format(time.RFC3339)
|
||||||
v.PaidAt = &s
|
v.PaidAt = &s
|
||||||
}
|
}
|
||||||
|
if row.Status == "created" && row.CreatedAt.Add(orderPendingTTL).Before(time.Now()) {
|
||||||
|
v.Status = "expired" // 展示层:台账仍 created,可继续支付/取消
|
||||||
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
@@ -289,3 +290,38 @@ func TestCatalog(t *testing.T) {
|
|||||||
t.Fatalf("catalog = %+v", resp.Items)
|
t.Fatalf("catalog = %+v", resp.Items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 老 created 单超 TTL → 列表视图显 expired;近期 created → 仍 created。
|
||||||
|
func TestList_ExpiredWhenStale(t *testing.T) {
|
||||||
|
router, st := newHandlerRig(t, nil)
|
||||||
|
ctx := context.Background()
|
||||||
|
// 直接构造两条 created 单,改 created_at 到很久以前 / 刚刚
|
||||||
|
if err := st.Insert(ctx, 1, "uuid-1", "pro_month", "old", "alipay", 2999, "CNY"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := st.Insert(ctx, 1, "uuid-1", "pro_month", "fresh", "alipay", 2999, "CNY"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// 把 old 的 created_at 回拨到 TTL 之前
|
||||||
|
if _, err := st.db.ExecContext(ctx,
|
||||||
|
`UPDATE pay_purchases SET created_at = ? WHERE out_trade_no = 'old'`,
|
||||||
|
time.Now().Add(-2*orderPendingTTL).UTC()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, authed(httptest.NewRequest(http.MethodGet, "/v1/pay/orders", nil), 1))
|
||||||
|
var resp struct {
|
||||||
|
Orders []orderView `json:"orders"`
|
||||||
|
}
|
||||||
|
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
got := map[string]string{}
|
||||||
|
for _, o := range resp.Orders {
|
||||||
|
got[o.OrderNo] = o.Status
|
||||||
|
}
|
||||||
|
if got["old"] != "expired" {
|
||||||
|
t.Fatalf("old 应过期, got %q", got["old"])
|
||||||
|
}
|
||||||
|
if got["fresh"] != "created" {
|
||||||
|
t.Fatalf("fresh 应 created, got %q", got["fresh"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user