feat(admin-payment): 支付订单 - 补充用户身份与详情

This commit is contained in:
MasterChiefCN authored and ddCat committed 2026-09-11 21:42:15 +08:00
1 parent 15e5b4a8bf
commit abc7481ac1
9 files changed
+201 -39

No files matched your search

+30 -6
View File
@@ -101,10 +101,22 @@ type PaymentOrderView struct {
}
type AdminPaymentOrderPage struct {
Orders []PaymentOrderView `json:"orders"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"pageSize"`
Orders []AdminPaymentOrderView `json:"orders"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"pageSize"`
}
type AdminPaymentOrderUser struct {
ID string `json:"id"`
Username string `json:"username"`
DisplayName string `json:"displayName"`
Email string `json:"email"`
}
type AdminPaymentOrderView struct {
PaymentOrderView
User *AdminPaymentOrderUser `json:"user"`
}
func (s *Service) PaymentNotificationResponse(providerID string, success bool) (int, string, string) {
@@ -876,9 +888,21 @@ func (s *Service) AdminPaymentOrderPage(actor *model.User, status, keyword strin
if err != nil {
return nil, err
}
views := make([]PaymentOrderView, 0, len(orders))
userIDs := make([]string, 0, len(orders))
for _, order := range orders {
views = append(views, paymentOrderView(order))
userIDs = append(userIDs, order.UserID)
}
users, err := s.repo.UsersByIDs(userIDs)
if err != nil {
return nil, err
}
views := make([]AdminPaymentOrderView, 0, len(orders))
for _, order := range orders {
view := AdminPaymentOrderView{PaymentOrderView: paymentOrderView(order)}
if user, ok := users[order.UserID]; ok {
view.User = &AdminPaymentOrderUser{ID: user.ID, Username: user.Username, DisplayName: user.DisplayName, Email: user.Email}
}
views = append(views, view)
}
return &AdminPaymentOrderPage{Orders: views, Total: total, Page: page, Limit: limit}, nil
}
@@ -0,0 +1,39 @@
package app
import (
"encoding/json"
"strings"
"testing"
)
func TestPaymentOrderIdentityOnlyInAdminList(t *testing.T) {
base := PaymentOrderView{ID: "order", UserID: "user", MerchantOrderNo: "merchant"}
ordinary, err := json.Marshal(base)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(ordinary), `"user":`) || strings.Contains(string(ordinary), `"email":`) {
t.Fatalf("ordinary response includes identity: %s", ordinary)
}
admin, err := json.Marshal(AdminPaymentOrderView{PaymentOrderView: base, User: &AdminPaymentOrderUser{ID: "user", Username: "alice", DisplayName: "小林", Email: "alice@example.com"}})
if err != nil {
t.Fatal(err)
}
var result struct {
ID string `json:"id"`
User *AdminPaymentOrderUser `json:"user"`
}
if err := json.Unmarshal(admin, &result); err != nil {
t.Fatal(err)
}
if result.ID != "order" || result.User == nil || result.User.Email != "alice@example.com" {
t.Fatalf("invalid admin response: %s", admin)
}
missing, err := json.Marshal(AdminPaymentOrderView{PaymentOrderView: base})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(missing), `"user":null`) || !strings.Contains(string(missing), `"userId":"user"`) {
t.Fatalf("missing user loses original identity: %s", missing)
}
}
+1 -1
View File
@@ -73,7 +73,7 @@ func (r *Repository) UsersByIDs(ids []string) (map[string]model.User, error) {
return map[string]model.User{}, nil
}
var users []model.User
if err := r.db.Select("id", "username", "display_name").Where("id IN ?", ids).Find(&users).Error; err != nil {
if err := r.db.Select("id", "username", "display_name", "email").Where("id IN ?", ids).Find(&users).Error; err != nil {
return nil, err
}
result := make(map[string]model.User, len(users))
+2 -1
View File
@@ -391,7 +391,8 @@ func (r *Repository) AdminPaymentOrders(status, keyword string, limit, offset in
}
if normalized := strings.TrimSpace(keyword); normalized != "" {
like := "%" + normalized + "%"
query = query.Where("merchant_order_no LIKE ? OR provider_trade_no LIKE ? OR user_id LIKE ?", like, like, like)
users := r.db.Model(&model.User{}).Select("id").Where("LOWER(username) LIKE ? OR LOWER(display_name) LIKE ? OR LOWER(email) LIKE ?", strings.ToLower(like), strings.ToLower(like), strings.ToLower(like))
query = query.Where("merchant_order_no LIKE ? OR provider_trade_no LIKE ? OR user_id = ? OR user_id IN (?)", like, like, normalized, users)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
@@ -363,6 +363,50 @@ func TestUnresolvedPaymentOrderCandidateCountOverlapping(t *testing.T) {
}
}
func TestAdminPaymentOrdersSearchUserIdentity(t *testing.T) {
db := openPaymentTestDB(t)
if err := db.AutoMigrate(&model.User{}); err != nil {
t.Fatal(err)
}
users := []model.User{
{ID: "user-alice", Username: "alice", DisplayName: "小林", Email: "alice@example.com"},
{ID: "user-bob", Username: "bob", DisplayName: "小林", Email: "bob@example.com"},
}
if err := db.Create(&users).Error; err != nil {
t.Fatal(err)
}
orders := []model.PaymentOrder{
{ID: "a", UserID: "user-alice", MerchantOrderNo: "merchant-a", IdempotencyKey: "a", Status: model.PaymentOrderPending},
{ID: "b", UserID: "user-bob", MerchantOrderNo: "merchant-b", IdempotencyKey: "b", Status: model.PaymentOrderClosed},
{ID: "c", UserID: "missing-user", MerchantOrderNo: "merchant-c", IdempotencyKey: "c", Status: model.PaymentOrderClosed},
}
tradeNo := "trade-c"
orders[2].ProviderTradeNo = &tradeNo
if err := db.Create(&orders).Error; err != nil {
t.Fatal(err)
}
repo := New(db)
for _, tc := range []struct {
keyword, status string
total int64
}{
{"ALICE@EXAMPLE.COM", "", 1}, {"alice", "", 1}, {"小林", "", 2},
{"小林", "pending", 1}, {"alice", "closed", 0}, {"user-bob", "", 1},
{"merchant-c", "", 1}, {"trade-c", "", 1}, {"missing-user", "", 1}, {"", "", 3},
} {
t.Run(tc.keyword+"/"+tc.status, func(t *testing.T) {
items, total, err := repo.AdminPaymentOrders(tc.status, tc.keyword, 1, 0)
if err != nil || total != tc.total || len(items) > 1 {
t.Fatalf("got items=%d total=%d err=%v; want total=%d", len(items), total, err, tc.total)
}
})
}
identities, err := repo.UsersByIDs([]string{"user-alice", "missing-user"})
if err != nil || len(identities) != 1 || identities["user-alice"].Email != "alice@example.com" {
t.Fatalf("identities=%v err=%v", identities, err)
}
}
func openPaymentTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared&_busy_timeout=5000"), &gorm.Config{})