小程序一些功能更新

This commit is contained in:
李琦
2026-09-29 10:57:01 +08:00
parent 0cb70a7618
commit 3bb7fa8db3
96 changed files with 13890 additions and 213 deletions

View File

@@ -0,0 +1,536 @@
package logic
import (
"context"
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
v1 "tool-api/api/admin/v1"
"tool-api/internal/consts"
"tool-api/internal/model/entity"
)
// ensureTestSchema 保证 member_orders 的退款相关列已存在。
// 说明:GoFrame 的 Update(Data(map)) 会**按表结构过滤掉未知列**(静默丢弃),
// 故运行期迁移 Migrate() 必须先执行,service_revoked / refunded_at 才会真正落库。
var migrateOnce sync.Once
func ensureTestSchema() {
migrateOnce.Do(func() { Migrate(context.Background()) })
}
// ============================================================================
// 订单退款 / 回收所购服务 · 集成测试(真实库,自建 fixture 并清理)
//
// 覆盖:会员套餐回收(减天数)、次数包回收(扣到 0 不为负)、不回收(权益保留)、
// 幂等(第二次调用不重复回收)、列表关键字命中。
// ============================================================================
func newTag(prefix string) string { return fmt.Sprintf("%s%d", prefix, time.Now().UnixNano()) }
func insertRefundUser(t *testing.T, tag, levelKey string, expire *gtime.Time) int64 {
t.Helper()
now := gtime.Now()
id, err := g.Model(consts.TableUsers).Data(g.Map{
"openid": "refund_" + tag,
"username": "refund_" + tag,
"nickname": "退款测试" + tag,
"level_key": levelKey,
"level_expire_at": expire, // nil → 写 NULL(永久)
"status": 1,
"created_at": now,
"updated_at": now,
}).InsertAndGetId()
if err != nil {
t.Fatalf("插入测试用户失败: %v", err)
}
return id
}
func insertDeliveredOrder(t *testing.T, userId int64, tag string, orderType int, fields g.Map) string {
t.Helper()
no := "TREF" + tag
now := gtime.Now()
data := g.Map{
"out_trade_no": no,
"wx_order_id": "wx_" + no,
"user_id": userId,
"openid": "refund_" + tag,
"order_type": orderType,
"plan_key": "",
"level_key": "",
"product_id": "test_product",
"price_cents": 1000,
"origin_price_cents": 1000,
"paid_price_cents": 1000,
"discount_cents": 0,
"promo_kind": 0,
"status": consts.OrderStatusDelivered,
"pay_channel": consts.PayChannelWx,
"created_at": now,
"updated_at": now,
"paid_at": now,
"delivered_at": now,
}
for k, v := range fields {
data[k] = v
}
if _, err := g.Model(consts.TableMemberOrders).Data(data).Insert(); err != nil {
t.Fatalf("插入测试订单失败: %v", err)
}
return no
}
func cleanupRefundFixture(t *testing.T, uid int64, orderNos ...string) {
t.Helper()
for _, no := range orderNos {
if _, err := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).Delete(); err != nil {
t.Logf("[cleanup] 删除订单 %s 失败: %v", no, err)
}
}
g.Model(consts.TableUserToolQuota).Where("user_id", uid).Delete()
g.Model(consts.TableUsers).Where("id", uid).Delete()
}
// TestAdminOrderRefund_RevokeMemberService 会员套餐退款回收:减去本单天数 + 幂等。
func TestAdminOrderRefund_RevokeMemberService(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("rm")
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, 60))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
// 读回基线(读回存在已知时区偏移,故一律用「读回 vs 读回」比较,偏移自然抵消)
baseU, _ := g.Model(consts.TableUsers).Where("id", uid).One()
baseline := baseU["level_expire_at"].GTime()
res, err := AdminOrderRefund(ctx, no, true)
if err != nil {
t.Fatalf("退款失败: %v", err)
}
if !res.Revoked || !res.ServiceRevoked {
t.Fatalf("期望 revoke=true 且执行退款,得 %+v", res)
}
row, _ := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).One()
if row["status"].Int() != consts.OrderStatusRefunded {
t.Fatalf("订单状态应为已退款(2),得 %d", row["status"].Int())
}
if row["service_revoked"].Int() != 1 {
t.Fatalf("service_revoked 应为 1,得 %d", row["service_revoked"].Int())
}
if row["refunded_at"].GTime() == nil || row["refunded_at"].GTime().IsZero() {
t.Fatalf("refunded_at 应已写入")
}
u, _ := g.Model(consts.TableUsers).Where("id", uid).One()
newExpire := u["level_expire_at"].GTime()
want := baseline.AddDate(0, 0, -30)
if newExpire == nil {
t.Fatalf("回收后期望仍为有限期(非 NULL)")
}
if diff := newExpire.Time.Sub(want.Time); diff < -2*time.Minute || diff > 2*time.Minute {
t.Fatalf("到期时间应回退 30 天:期望≈%s,得 %s", want.String(), newExpire.String())
}
if u["level_key"].String() != "v2" {
t.Fatalf("回收后仍未到期,等级不应降级,得 %q", u["level_key"].String())
}
// 幂等:再次退款返回成功但不重复回收
res2, err := AdminOrderRefund(ctx, no, true)
if err != nil {
t.Fatalf("幂等退款失败: %v", err)
}
if !res2.AlreadyRefunded || res2.Revoked {
t.Fatalf("第二次应为幂等命中,得 %+v", res2)
}
u2, _ := g.Model(consts.TableUsers).Where("id", uid).One()
if !u2["level_expire_at"].GTime().Time.Equal(newExpire.Time) {
t.Fatalf("幂等调用不应重复回收:%s → %s", newExpire.String(), u2["level_expire_at"].String())
}
}
// TestAdminOrderRefund_RevokeMemberExpiredDowngrade 回收后已到期 → 期限置当前时刻并回落默认等级。
func TestAdminOrderRefund_RevokeMemberExpiredDowngrade(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("rd")
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, 10))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
if _, err := AdminOrderRefund(ctx, no, true); err != nil {
t.Fatalf("退款失败: %v", err)
}
u, _ := g.Model(consts.TableUsers).Where("id", uid).One()
expire := u["level_expire_at"].GTime()
if expire == nil {
t.Fatalf("回收后期限不应为 NULL(不得写成永久)")
}
if expire.After(gtime.Now()) {
t.Fatalf("回收后应已到期,得 %s", expire.String())
}
// 等级回落经 EffectiveLevelKey 收敛(不直接改写 level_key,与自然到期口径一致)
eff := EffectiveLevelKey(&entity.Users{LevelKey: u["level_key"].String(), LevelExpireAt: expire})
if eff != consts.DefaultLevelKey {
t.Fatalf("回收后已到期,生效等级应回落默认,得 %q", eff)
}
}
// TestAdminOrderRefund_RevokeQuotaClamp 次数包退款回收:扣到 0 为止,不为负。
func TestAdminOrderRefund_RevokeQuotaClamp(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("rq")
toolKey := "refund_test_tool_" + tag
uid := insertRefundUser(t, tag, consts.DefaultLevelKey, nil)
no1 := insertDeliveredOrder(t, uid, tag+"a", consts.OrderTypeQuota, g.Map{
"pack_key": "pack5", "tool_key": toolKey, "times": 5, "duration_days": 365,
})
no2 := insertDeliveredOrder(t, uid, tag+"b", consts.OrderTypeQuota, g.Map{
"pack_key": "pack10", "tool_key": toolKey, "times": 10, "duration_days": 365,
})
defer cleanupRefundFixture(t, uid, no1, no2)
now := gtime.Now()
if _, err := g.Model(consts.TableUserToolQuota).Data(g.Map{
"user_id": uid, "tool_key": toolKey, "times_left": 8, "total_bought": 8,
"expire_at": now.AddDate(0, 0, 365), "created_at": now, "updated_at": now,
}).Insert(); err != nil {
t.Fatalf("插入额度失败: %v", err)
}
// 第一次:8 - 5 = 3
if _, err := AdminOrderRefund(ctx, no1, true); err != nil {
t.Fatalf("退款失败: %v", err)
}
q, _ := g.Model(consts.TableUserToolQuota).Where("user_id", uid).Where("tool_key", toolKey).One()
if q["times_left"].Int() != 3 {
t.Fatalf("次数回收后应剩 3,得 %d", q["times_left"].Int())
}
// 第二次:3 - 10 → 夹到 0(不为负)
if _, err := AdminOrderRefund(ctx, no2, true); err != nil {
t.Fatalf("退款失败: %v", err)
}
q2, _ := g.Model(consts.TableUserToolQuota).Where("user_id", uid).Where("tool_key", toolKey).One()
if q2["times_left"].Int() != 0 {
t.Fatalf("次数应扣到 0 为止,得 %d", q2["times_left"].Int())
}
if q2["total_bought"].Int() < q2["times_left"].Int() {
t.Fatalf("total_bought 不应小于 times_left:%d < %d", q2["total_bought"].Int(), q2["times_left"].Int())
}
}
// TestAdminOrderRefund_PermanentMemberRevoked 永久会员(level_expire_at IS NULL)退款回收:
// 用户裁定(2026-09-23)—— 管理员勾选「取消服务」时,永久授权**同样真取消、置为已过期**。
//
// 关键反转点(相对旧行为「保留服务」):
// 1. level_expire_at 不再为 NULL,且 ≈ 当前时刻(由 DB 侧 NOW() 写入,读写同源、无 8h 时区偏差);
// 2. EffectiveLevelKey 随即回落默认等级(降级生效);
// 3. level_key 字段本身**保留原值**(失效判定交给 EffectiveLevelKey,不为永久会员加特例清空)。
func TestAdminOrderRefund_PermanentMemberRevoked(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("pm")
uid := insertRefundUser(t, tag, "v2", nil) // level_expire_at = NULL → 永久
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
before := gtime.Now()
res, err := AdminOrderRefund(ctx, no, true)
if err != nil {
t.Fatalf("退款失败: %v", err)
}
if !res.Revoked || !res.ServiceRevoked {
t.Fatalf("期望 revoke=true 且确已回收永久授权,得 %+v", res)
}
row, _ := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).One()
if row["status"].Int() != consts.OrderStatusRefunded {
t.Fatalf("订单状态应为已退款(2),得 %d", row["status"].Int())
}
if row["service_revoked"].Int() != 1 {
t.Fatalf("service_revoked 应为 1,得 %d", row["service_revoked"].Int())
}
u, _ := g.Model(consts.TableUsers).Where("id", uid).One()
expire := u["level_expire_at"].GTime()
// 关键反转点 1:永久(NULL)已被置为「当前时刻」,不再为 NULL
if expire == nil || expire.IsZero() {
t.Fatalf("永久会员退款回收后 level_expire_at 不应再为 NULL")
}
// DB 侧 NOW() 写入 + 读侧同源,容许 ±2 分钟
if d := expire.Time.Sub(before.Time); d < -2*time.Minute || d > 2*time.Minute {
t.Fatalf("level_expire_at 应≈当前时刻:期望≈%s,得 %s", before.String(), expire.String())
}
// 关键反转点 3:level_key 字段本身保留原值,不清空
if u["level_key"].String() != "v2" {
t.Fatalf("level_key 字段应保留原值 v2,得 %q", u["level_key"].String())
}
// 关键反转点 2:生效等级已回落默认(降级生效)。
// EffectiveLevelKey 内部以严格 `<` 判过期;而取消时刻由 DB 侧 NOW() 写入,因秒级截断与
// DB/进程时钟的亚秒偏差,读回值可能恰落在「当前秒」甚至略晚。故先等待进程时钟越过该时刻,
// 再验证降级已生效(<= ~2s,确定性,不受 DB 时钟偏差影响)。
if wait := time.Until(expire.Time.Add(time.Second)); wait > 0 {
time.Sleep(wait)
}
if eff := EffectiveLevelKey(&entity.Users{LevelKey: u["level_key"].String(), LevelExpireAt: expire}); eff != consts.DefaultLevelKey {
t.Fatalf("永久会员被回收后生效等级应回落默认,得 %q", eff)
}
}
// TestAdminOrderRefund_RevokeButNothingToReclaim 勾选「取消服务」但用户当前无生效会员(已过期):
// service_revoked 按**实际回收结果**记录 → 0(列表不显示「已回收」,避免误导管理员)。
func TestAdminOrderRefund_RevokeButNothingToReclaim(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("rn")
// level_key=v2 但 level_expire_at 早于当前时刻 → 当前无生效会员(已过期)
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, -1))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
res, err := AdminOrderRefund(ctx, no, true) // 明确勾选回收
if err != nil {
t.Fatalf("退款失败: %v", err)
}
if !res.Revoked {
t.Fatalf("订单应被标记退款,得 %+v", res)
}
if res.ServiceRevoked {
t.Fatalf("用户当前无生效会员,service_revoked 应为 false,得 %+v", res)
}
row, _ := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).One()
if row["status"].Int() != consts.OrderStatusRefunded {
t.Fatalf("订单状态应为已退款(2),得 %d", row["status"].Int())
}
if row["service_revoked"].Int() != 0 {
t.Fatalf("无可回收的生效授权时 service_revoked 应为 0,得 %d", row["service_revoked"].Int())
}
// 权益本就无效,退款回收后仍为「已过期」(不产生新的生效会员)
u, _ := g.Model(consts.TableUsers).Where("id", uid).One()
eff := EffectiveLevelKey(&entity.Users{
LevelKey: u["level_key"].String(),
LevelExpireAt: u["level_expire_at"].GTime(),
})
if eff != consts.DefaultLevelKey {
t.Fatalf("已过期会员退款回收后不应变为生效,得 %q", eff)
}
}
// TestAdminOrderRefund_NewWritesNoTZ01Drift 钉住「新代码不得引入 TZ-01」:
// 退款流程新引入的 datetime 写入(订单 refunded_at / updated_at、额度 updated_at、永久会员 level_expire_at)
// 一律走库内 NOW(),库读回后应≈当前墙钟(±5s),而**不是**早 8h。
func TestAdminOrderRefund_NewWritesNoTZ01Drift(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
// ---- (1) 会员单退款:订单 refunded_at / updated_at ≈ 墙钟 ----
tag := newTag("tzm")
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, 60))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
before := gtime.Now()
if _, err := AdminOrderRefund(ctx, no, true); err != nil {
t.Fatalf("退款失败: %v", err)
}
row, _ := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).One()
refunded := row["refunded_at"].GTime()
if refunded == nil || refunded.IsZero() {
t.Fatalf("refunded_at 应写入且非零")
}
if d := refunded.Time.Sub(before.Time); d < -5*time.Second || d > 5*time.Second {
t.Fatalf("refunded_at 应≈墙钟(±5s),得 %s(疑似 TZ-01 8h 偏差)", refunded.String())
}
if d := row["updated_at"].GTime().Time.Sub(before.Time); d < -5*time.Second || d > 5*time.Second {
t.Fatalf("订单 updated_at 应≈墙钟(±5s),得 %s(疑似 TZ-01 8h 偏差)", row["updated_at"].String())
}
// ---- (2) 永久会员退款:level_expire_at 非 NULL 且不晚于库内 NOW() ----
tag2 := newTag("tzp")
uid2 := insertRefundUser(t, tag2, "v2", nil) // 永久
no2 := insertDeliveredOrder(t, uid2, tag2, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid2, no2)
before2 := gtime.Now()
if _, err := AdminOrderRefund(ctx, no2, true); err != nil {
t.Fatalf("退款失败: %v", err)
}
u2, _ := g.Model(consts.TableUsers).Where("id", uid2).One()
exp := u2["level_expire_at"].GTime()
if exp == nil || exp.IsZero() {
t.Fatalf("永久会员回收后 level_expire_at 不应为 NULL")
}
// 库内判定「不晚于 NOW()」:比 Go 侧严格 `<` 更稳,规避秒级边界。
late, _ := g.DB().GetValue(ctx,
"SELECT (level_expire_at <= NOW()) FROM "+consts.TableUsers+" WHERE id = ?", uid2)
if !late.Bool() {
t.Fatalf("永久会员回收后 level_expire_at 应不晚于库内 NOW(),得 %s", exp.String())
}
if d := exp.Time.Sub(before2.Time); d < -5*time.Second || d > 5*time.Second {
t.Fatalf("level_expire_at 应≈墙钟(±5s),得 %s(疑似 TZ-01 8h 偏差)", exp.String())
}
// ---- (3) 次数包退款:额度 updated_at ≈ 墙钟 ----
tag3 := newTag("tzq")
toolKey := "tz_tool_" + tag3
uid3 := insertRefundUser(t, tag3, consts.DefaultLevelKey, nil)
no3 := insertDeliveredOrder(t, uid3, tag3, consts.OrderTypeQuota, g.Map{
"pack_key": "pack5", "tool_key": toolKey, "times": 5, "duration_days": 365,
})
defer cleanupRefundFixture(t, uid3, no3)
tn := gtime.Now()
if _, err := g.Model(consts.TableUserToolQuota).Data(g.Map{
"user_id": uid3, "tool_key": toolKey, "times_left": 8, "total_bought": 8,
"expire_at": tn.AddDate(0, 0, 365), "created_at": tn, "updated_at": tn,
}).Insert(); err != nil {
t.Fatalf("插入额度失败: %v", err)
}
beforeQ := gtime.Now()
if _, err := AdminOrderRefund(ctx, no3, true); err != nil {
t.Fatalf("退款失败: %v", err)
}
q, _ := g.Model(consts.TableUserToolQuota).Where("user_id", uid3).Where("tool_key", toolKey).One()
if d := q["updated_at"].GTime().Time.Sub(beforeQ.Time); d < -5*time.Second || d > 5*time.Second {
t.Fatalf("额度 updated_at 应≈墙钟(±5s),得 %s(疑似 TZ-01 8h 偏差)", q["updated_at"].String())
}
}
// TestAdminOrderRefund_NoRevokeKeepsService 不回收服务:仅改状态,权益保留。
func TestAdminOrderRefund_NoRevokeKeepsService(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("nr")
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, 60))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
baseU, _ := g.Model(consts.TableUsers).Where("id", uid).One()
baseline := baseU["level_expire_at"].GTime()
res, err := AdminOrderRefund(ctx, no, false)
if err != nil {
t.Fatalf("退款失败: %v", err)
}
if !res.Revoked || res.ServiceRevoked {
t.Fatalf("不应回收服务,得 %+v", res)
}
row, _ := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).One()
if row["status"].Int() != consts.OrderStatusRefunded || row["service_revoked"].Int() != 0 {
t.Fatalf("状态/service_revoked 不符:%d/%d", row["status"].Int(), row["service_revoked"].Int())
}
u, _ := g.Model(consts.TableUsers).Where("id", uid).One()
if !u["level_expire_at"].GTime().Time.Equal(baseline.Time) {
t.Fatalf("不回收时会员期限应保持不变:%s → %s", baseline.String(), u["level_expire_at"].String())
}
}
// TestAdminOrderRefund_StatusGuard 非已发货订单退款被拒(可读业务错误,4015)。
func TestAdminOrderRefund_StatusGuard(t *testing.T) {
ensureTestSchema()
ctx := context.Background()
tag := newTag("sg")
uid := insertRefundUser(t, tag, consts.DefaultLevelKey, nil)
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
"status": consts.OrderStatusPending,
})
defer cleanupRefundFixture(t, uid, no)
if _, err := AdminOrderRefund(ctx, no, true); err == nil {
t.Fatalf("待支付订单退款应被拒绝")
}
}
// TestAdminOrderList_Keyword 列表关键字命中订单号,且返回商品名与用户信息。
func TestAdminOrderList_Keyword(t *testing.T) {
ctx := context.Background()
tag := newTag("ls")
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, 60))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
defer cleanupRefundFixture(t, uid, no)
res, err := AdminOrderList(ctx, &v1.OrderListReq{Keyword: no})
if err != nil {
t.Fatalf("列表失败: %v", err)
}
if res.Total < 1 {
t.Fatalf("按订单号应至少命中 1 条,得 %d", res.Total)
}
found := false
for _, it := range res.List {
if it.OutTradeNo == no {
found = true
if it.Nickname == "" || it.UserId != uid {
t.Fatalf("应带归属用户信息,得 %+v", it)
}
if it.StatusText == "" {
t.Fatalf("应带状态文案")
}
}
}
if !found {
t.Fatalf("未在列表中找到目标订单 %s", no)
}
}
// ============================================================================
// 供 curl 实测用的可选 fixture(默认不运行;设 SEED_REFUND=1 时插入并保留)
// ============================================================================
func TestAdminOrderSeedForCurl(t *testing.T) {
if os.Getenv("SEED_REFUND") != "1" {
t.Skip("未设置 SEED_REFUND=1,跳过 curl fixture 播种")
}
tag := "curl" + fmt.Sprintf("%d", time.Now().UnixNano())
uid := insertRefundUser(t, tag, "v2", gtime.Now().AddDate(0, 0, 60))
no := insertDeliveredOrder(t, uid, tag, consts.OrderTypeMember, g.Map{
"plan_key": "vip-month", "level_key": "v2", "duration_days": 30,
})
fmt.Printf("SEED_REFUND uid=%d out_trade_no=%s\n", uid, no)
}
// TestAdminOrderCleanupForCurl 清理 curl fixture(设 CLEAN_REFUND=1 + REFUND_ORDER_NO/REFUND_UID)。
func TestAdminOrderCleanupForCurl(t *testing.T) {
if os.Getenv("CLEAN_REFUND") != "1" {
t.Skip("未设置 CLEAN_REFUND=1,跳过 curl fixture 清理")
}
no := os.Getenv("REFUND_ORDER_NO")
uid := os.Getenv("REFUND_UID")
if no != "" {
if _, err := g.Model(consts.TableMemberOrders).Where("out_trade_no", no).Delete(); err != nil {
t.Logf("删除订单失败: %v", err)
}
}
if uid != "" {
g.Model(consts.TableUserToolQuota).Where("user_id", uid).Delete()
g.Model(consts.TableUsers).Where("id", uid).Delete()
}
fmt.Printf("CLEAN_REFUND done no=%s uid=%s\n", no, uid)
}