feat: ¥1 首月特惠套餐(新店专享,每店限购一次,官网+App 同步上线)
- 后端:payPlans 新增 promo_first_month(¥1/30 天/标准版权益);下单强校验每店限购一次(仅 paid 计数,弃单可重下),已享用返回 409;新增 GET /license/promo-status 供前端置灰 - 官网:checkout 套餐段第二位插「🔥 首月特惠 ¥1」+ 推广横幅 + 划线原价 ¥299;首页价格区第二位插特惠卡(5 列);已享用自动置灰 - App:购买弹窗套餐段改三档(标准/特惠/高级),特惠选中显示推广横幅与划线价,已享用置灰并禁用提交 - 实付金额以 pay 侧为准,pay 需 seed promo_first_month 产品后生效 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -275,6 +275,42 @@ func TestCreatePurchase_UnknownPlanAndUnconfigured(t *testing.T) {
|
||||
assert.ErrorIs(t, err, ErrPayNotConfigured)
|
||||
}
|
||||
|
||||
// ---------- 首月特惠限购 ----------
|
||||
|
||||
func TestCreatePurchase_PromoOncePerShop(t *testing.T) {
|
||||
db := testutil.SetupTestDB()
|
||||
shop := testutil.CreateTestShop(db, "PAY009")
|
||||
other := testutil.CreateTestShop(db, "PAY010")
|
||||
svc := newTestPaySvc(db, "http://pay.invalid")
|
||||
|
||||
// 未买过:不触发限购(pay 地址无效会走到下单失败,但不是 ErrPromoUsed)
|
||||
used, err := svc.PromoUsed(shop.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, used)
|
||||
_, err = svc.CreatePurchase(shop.ID, 1, PromoBizCode)
|
||||
assert.NotErrorIs(t, err, ErrPromoUsed)
|
||||
|
||||
// pending 单不算已享用(可能弃单),仍可重新下单
|
||||
createPendingPurchase(t, db, shop.ID, PromoBizCode, "1.00", "yanmei-promo-0")
|
||||
used, err = svc.PromoUsed(shop.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, used, "pending 不算已享用")
|
||||
|
||||
// 已支付的特惠单存在 → 已享用,再购直接拒绝
|
||||
p := createPendingPurchase(t, db, shop.ID, PromoBizCode, "1.00", "yanmei-promo-1")
|
||||
require.NoError(t, db.Model(p).Update("status", "paid").Error)
|
||||
used, err = svc.PromoUsed(shop.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, used)
|
||||
_, err = svc.CreatePurchase(shop.ID, 1, PromoBizCode)
|
||||
assert.ErrorIs(t, err, ErrPromoUsed)
|
||||
|
||||
// 多租户隔离:别家买过不影响本店
|
||||
used, err = svc.PromoUsed(other.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, used)
|
||||
}
|
||||
|
||||
// ---------- Status ----------
|
||||
|
||||
func TestStatus_ScopedToShop(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user