feat: add invite rewards and redeem codes
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# Invite Protocol (Frontend <-> Backend)
|
||||
|
||||
This document defines the invite code contract for authenticated users.
|
||||
This document defines the invite/referral contract for authenticated web users.
|
||||
|
||||
Protocol verification status:
|
||||
|
||||
- Backend route source: `backend/src/v1/invite/router.py`
|
||||
- Backend service source: `backend/src/v1/invite/service.py`
|
||||
- Backend schema source: `backend/src/v1/invite/schemas.py`
|
||||
- Frontend mapping source: `apps/lib/features/settings/data/apis/invite_api.dart`
|
||||
- Web mapping source: `web/src/lib/api.ts`
|
||||
|
||||
## Compatibility strategy
|
||||
|
||||
@@ -18,7 +18,7 @@ Protocol verification status:
|
||||
|
||||
### GET /api/v1/invite/me
|
||||
|
||||
Get the current user's invite code information.
|
||||
Get the current user's invite overview.
|
||||
|
||||
**Authorization**: Requires authenticated session. User identity from JWT `sub`.
|
||||
|
||||
@@ -26,15 +26,78 @@ Get the current user's invite code information.
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "ABC123XYZ",
|
||||
"used_count": 5
|
||||
"myCode": "ABC123",
|
||||
"binding": {
|
||||
"canBind": false,
|
||||
"boundInviteCode": "QWE789",
|
||||
"boundAt": "2026-05-21T10:30:00+00:00"
|
||||
},
|
||||
"summary": {
|
||||
"rewardPoints": 40,
|
||||
"invitedCount": 3,
|
||||
"rewardedCount": 2,
|
||||
"pendingCount": 1,
|
||||
"rewardedPoints": 80,
|
||||
"totalPotentialRewardPoints": 120
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"referralId": "5ed7c3f0-6d1c-4f3f-8b40-2cb537da53e6",
|
||||
"inviteCode": "ABC123",
|
||||
"boundAt": "2026-05-18T09:00:00+00:00",
|
||||
"firstCreemPaidAt": "2026-05-20T11:15:00+00:00",
|
||||
"rewardGranted": true,
|
||||
"rewardGrantedAt": "2026-05-20T11:15:02+00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Field rules:
|
||||
|
||||
- `code`: string, unique invite code assigned to the user
|
||||
- `used_count`: integer `>= 0`, number of times this code has been used
|
||||
- `myCode`: string, unique invite code assigned to the current user
|
||||
- `binding.canBind`: boolean, whether the current user can still bind another user's invite code
|
||||
- `binding.boundInviteCode`: string or `null`, bound inviter code snapshot
|
||||
- `binding.boundAt`: ISO 8601 datetime or `null`
|
||||
- `summary.rewardPoints`: integer `>= 0`, reward granted to inviter and invitee per qualified post-bind CREEM payment
|
||||
- `summary.invitedCount`: integer `>= 0`
|
||||
- `summary.rewardedCount`: integer `>= 0`
|
||||
- `summary.pendingCount`: integer `>= 0`, computed as `invitedCount - rewardedCount`
|
||||
- `summary.rewardedPoints`: integer `>= 0`, computed as `rewardedCount * rewardPoints`
|
||||
- `summary.totalPotentialRewardPoints`: integer `>= 0`, computed as `invitedCount * rewardPoints`
|
||||
- `items`: inviter-side referral list, newest first
|
||||
- `items[].firstCreemPaidAt`: present only when the invitee has completed the qualifying CREEM payment after binding
|
||||
- `items[].rewardGranted`: whether inviter-side invite reward has been credited
|
||||
- `items[].rewardGrantedAt`: present only when `rewardGranted=true`
|
||||
|
||||
### POST /api/v1/invite/bind
|
||||
|
||||
Bind another user's invite code to the current account.
|
||||
|
||||
This endpoint is write-once:
|
||||
|
||||
- a user can bind at most once;
|
||||
- binding cannot be removed;
|
||||
- self-binding is forbidden;
|
||||
- binding is allowed even if the current user already has completed CREEM payments.
|
||||
|
||||
**Authorization**: Requires authenticated session.
|
||||
|
||||
**Request**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "ABC123"
|
||||
}
|
||||
```
|
||||
|
||||
Field rules:
|
||||
|
||||
- `code`: string, exactly 6 uppercase alphanumeric invite characters after normalization
|
||||
|
||||
**Response (200)**:
|
||||
|
||||
Same shape as `GET /api/v1/invite/me` after the bind succeeds.
|
||||
|
||||
## Error contract linkage
|
||||
|
||||
@@ -42,8 +105,14 @@ Field rules:
|
||||
- Shared registry: `docs/protocols/common/http-error-codes.md`.
|
||||
- Error codes for this feature:
|
||||
- `INVITE_CODE_NOT_FOUND` (404): Invite code not found for current user
|
||||
- `INVITE_BIND_CODE_NOT_FOUND` (404): Invite code to bind does not exist
|
||||
- `INVITE_ALREADY_BOUND` (409): Current user already bound an inviter
|
||||
- `INVITE_SELF_BIND_FORBIDDEN` (409): Current user attempted to bind their own code
|
||||
- `INVITE_CODE_NOT_BINDABLE` (409): Invite code is disabled, expired, or has no owner
|
||||
- `INVITE_CODE_INVALID` (422): Invite code format is invalid
|
||||
|
||||
## Data model linkage
|
||||
|
||||
- Invite codes are stored in `invite_codes` table.
|
||||
- See `docs/protocols/common/user-points-chat-data-protocol.md` for `profiles.referred_by` field.
|
||||
- Referral bindings are stored in `invite_referrals`.
|
||||
- See `docs/protocols/common/user-points-chat-data-protocol.md` for `profiles.referred_by`, `invite_referrals`, `redeem_code_batches`, and `redeem_codes`.
|
||||
|
||||
Reference in New Issue
Block a user