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"`
|
||||
}
|
||||
|
||||
// 未付订单展示层过期窗口(近似 pay 会话时效;仅影响列表/详情显示,不改台账)。
|
||||
const orderPendingTTL = 30 * time.Minute
|
||||
|
||||
func orderViewFromRow(row *PurchaseRow) orderView {
|
||||
v := orderView{
|
||||
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)
|
||||
v.PaidAt = &s
|
||||
}
|
||||
if row.Status == "created" && row.CreatedAt.Add(orderPendingTTL).Before(time.Now()) {
|
||||
v.Status = "expired" // 展示层:台账仍 created,可继续支付/取消
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
@@ -289,3 +290,38 @@ func TestCatalog(t *testing.T) {
|
||||
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