feat(invite): 邀请奖励金账户(与金币隔离) + 比价发奖 + 奖励金提现

- 新增邀请奖励金独立账本: coin_account 加余额列 + invite_cash_transaction 流水表, 与金币现金物理隔离
- bind 改 v2: 绑定只发被邀请人新人金币; 邀请人改在好友首次成功比价时发 2 元奖励金(幂等只发一次)
- 提现链路按 source 分流: 扣款/退款/对账/分页各认账户, 两本账不串
- /invite/me 返回奖励金余额/累计提现/7天倒计时
- 新增 migration(建表加列) + 比价发奖与提现隔离测试
This commit is contained in:
xiebing
2026-06-26 15:50:20 +08:00
parent e13f0f7658
commit 5adf90dbff
16 changed files with 910 additions and 172 deletions
+10 -10
View File
@@ -11,7 +11,6 @@ from sqlalchemy import select
from app.core.rewards import (
INVITE_FP_WINDOW_DAYS,
INVITE_INVITEE_COINS,
INVITE_INVITER_COINS,
INVITE_NEW_USER_WINDOW_HOURS,
)
from app.db.session import SessionLocal
@@ -56,8 +55,8 @@ def test_invite_me_returns_stable_code(client) -> None:
assert _my_code(client, token) == body["invite_code"]
def test_bind_flow_both_get_coins(client) -> None:
"""B 用 A 的码绑定 → 双方各得 1 万金币;A 战绩 +1"""
def test_bind_flow_invitee_gets_coins(client) -> None:
"""v2:B 用 A 的码绑定 → 只有被邀请人 B 得新人金币;邀请人 A 绑定时不发(改比价发现金)"""
a = _login(client, "13800002002")
b = _login(client, "13800002003")
a_code = _my_code(client, a)
@@ -71,14 +70,14 @@ def test_bind_flow_both_get_coins(client) -> None:
assert res["status"] == "success"
assert res["coins_awarded"] == INVITE_INVITEE_COINS
# 双方金币到账
# 被邀请人金币到账;邀请人绑定时不发金币(v2 改"好友比价发 2 元邀请奖励金")
assert _coin_balance(client, b) == INVITE_INVITEE_COINS
assert _coin_balance(client, a) == INVITE_INVITER_COINS
assert _coin_balance(client, a) == 0
# A 的战绩:已邀 1 人,累计获得 = 邀请人那份
# A 的战绩:已邀 1 人;金币口径收益为 0(邀请人收益改走邀请奖励金,见 try_reward_on_compare)
r = client.get("/api/v1/invite/me", headers=_auth(a))
assert r.json()["invited_count"] == 1
assert r.json()["coins_earned"] == INVITE_INVITER_COINS
assert r.json()["coins_earned"] == 0
def test_bind_idempotent_no_double_reward(client) -> None:
@@ -217,8 +216,8 @@ def test_bind_by_fingerprint_success(client) -> None:
)
assert r2.status_code == 200, r2.text
assert r2.json()["status"] == "success"
# 双方各发金币
assert _coin_balance(client, a) == INVITE_INVITER_COINS
# v2:只被邀请人发新人金币;邀请人绑定不发(改比价发现金)
assert _coin_balance(client, a) == 0
assert _coin_balance(client, b) == INVITE_INVITEE_COINS
@@ -322,7 +321,8 @@ def test_invitees_basic(client) -> None:
names = {it["display_name"] for it in body["items"]}
assert names == expected_names
assert all(it["avatar_url"] is None for it in body["items"])
assert all(it["coins"] == INVITE_INVITER_COINS for it in body["items"])
# v2:绑定时邀请人那份为 0(收益改"好友比价才发奖"),故每条 coins 字段=0
assert all(it["coins"] == 0 for it in body["items"])
def test_invitees_order_desc(client) -> None: