feat: add invite rewards and redeem codes

This commit is contained in:
zl-q
2026-05-21 16:26:58 +08:00
parent d712645754
commit 673f8fed30
67 changed files with 3813 additions and 265 deletions
@@ -4,9 +4,9 @@ This protocol defines the canonical data contract for user profile, points accou
Protocol verification status:
- Last audited migration: `backend/alembic/versions/20260413_0004_register_bonus_claims_snapshot.py`
- Last audited models: `backend/src/models/profile.py`, `backend/src/models/user_points.py`, `backend/src/models/points_ledger.py`, `backend/src/models/points_audit_ledger.py`, `backend/src/models/register_bonus_claims.py`, `backend/src/models/agent_chat_session.py`, `backend/src/models/agent_chat_message.py`
- Current status: aligned with register bonus moved to application service
- Last audited migration: `backend/alembic/versions/20260521_0002_invite_referrals_and_redeem_codes.py`
- Last audited models: `backend/src/models/profile.py`, `backend/src/models/user_points.py`, `backend/src/models/points_ledger.py`, `backend/src/models/points_audit_ledger.py`, `backend/src/models/register_bonus_claims.py`, `backend/src/models/invite_referral.py`, `backend/src/models/redeem_code_batch.py`, `backend/src/models/redeem_code.py`, `backend/src/models/system_audit_log.py`, `backend/src/models/agent_chat_session.py`, `backend/src/models/agent_chat_message.py`
- Current status: aligned with referral rewards and redeem code activation
## Scope
@@ -15,6 +15,10 @@ Protocol verification status:
- `points_ledger`
- `points_audit_ledger`
- `register_bonus_claims`
- `invite_referrals`
- `redeem_code_batches`
- `redeem_codes`
- `system_audit_logs`
- `sessions`
- `messages`
@@ -23,6 +27,7 @@ Protocol verification status:
- Current strategy: additive evolution.
- Breaking changes (drop/rename/type change on core fields) require explicit migration + rollback notes.
- `points_ledger.metadata.schema_version` is mandatory and current value is `1`.
- Invite reward amount is configured by backend env `ERYAO_POINTS_POLICY__INVITE_REWARD_POINTS`.
## Runtime charging policy (chat)
@@ -53,6 +58,14 @@ Protocol verification status:
Note: `register` and `adjust` do not bind to any `biz_type` (they are `null`).
### `adjust` metadata ext.reason conventions
For referral rewards and redeem codes, `change_type='adjust'` must use one of:
- `invite_reward_inviter`
- `invite_reward_invitee`
- `redeem_code_activation`
## Table contract
### profiles
@@ -129,6 +142,87 @@ Note: `register` and `adjust` do not bind to any `biz_type` (they are `null`).
- `balance_snapshot` stores the latest pre-delete account balance for same-email re-registration recovery
- `has_purchased_starter_pack` tracks whether user has purchased the starter pack ($0.99/60 credits)
### invite_referrals
- PK: `id`
- FK:
- `inviter_user_id -> profiles.id`
- `invitee_user_id -> profiles.id`
- `invite_code_id -> invite_codes.id`
- `first_creem_transaction_id -> creem_transactions.id` (`on delete set null`)
- Core fields:
- `invite_code_snapshot`
- `bound_at`
- `first_creem_paid_at`
- `inviter_reward_event_id`
- `invitee_reward_event_id`
- `inviter_reward_granted_at`
- `invitee_reward_granted_at`
- `created_at`
- `updated_at`
- Constraints:
- one invitee can only appear once
- inviter cannot equal invitee
- invite code snapshot is immutable after bind
- Notes:
- historical bindings should be backfilled from `profiles.referred_by`
- reward grant is idempotent per side via unique event ids
### redeem_code_batches
- PK: `id`
- Core fields:
- `batch_key`
- `created_by`
- `notes`
- `created_at`
- `updated_at`
- Constraints:
- `batch_key` unique
### redeem_codes
- PK: `id`
- FK:
- `batch_id -> redeem_code_batches.id`
- `redeemed_by_user_id -> auth.users.id` (`on delete set null`)
- Core fields:
- `code`
- `package_product_code`
- `package_type`
- `package_name_snapshot`
- `credits`
- `sort_order`
- `status`
- `redeemed_at`
- `redeem_event_id`
- `created_at`
- `updated_at`
- Constraints:
- `code` unique
- `status in ('active', 'redeemed', 'disabled')`
- redeemed rows must have `redeemed_at`, `redeemed_by_user_id`, `redeem_event_id`
- Notes:
- only regular packages are eligible for batch generation in this feature
- redeem codes do not qualify as CREEM payments for invite binding rewards
### system_audit_logs
- PK: `id`
- Core fields:
- `actor_user_id`
- `target_user_id`
- `action`
- `entity_type`
- `entity_id`
- `metadata`
- `created_at`
- `updated_at`
- Constraints:
- metadata must be object
- Notes:
- used for invite bind, invite reward grant, redeem code batch generation, redeem code activation
#### points_ledger.metadata (schema_version=1)
Canonical shape:
@@ -174,6 +268,7 @@ JSON constraints:
- Application service (in `POST /auth/email-session`):
- `grant_register_bonus_if_eligible()` restores `balance_snapshot` first when present; otherwise grants register bonus via `register_bonus_claims`
- Bonus amount from `config.points_policy.register_bonus_points`
- referral binding remains write-once after signup via API or historical trigger snapshot
### sessions
@@ -260,6 +355,13 @@ Returns the authenticated user's points ledger in reverse chronological order.
}
```
## Invite and redeem integration notes
- Invite binding is write-once, cannot be unbound, and is allowed regardless of previous completed `creem_transactions` rows.
- Referral reward qualification is based on the first completed CREEM payment after the invite binding has been created.
- Invite rewards are credited as `adjust` ledger rows with reason `invite_reward_inviter` / `invite_reward_invitee`.
- Redeem code activation is credited as an `adjust` ledger row with reason `redeem_code_activation`.
**Fields:**
- `items`: ledger rows ordered by `createdAt desc`
- `direction`: `1` for income, `-1` for spending/deduction