Skip to content

邀请码 API

邀请码管理接口,包括公开验证/绑定接口和需认证的用户接口。此为可插拔功能模块。

路由前缀/invite-codes

源码apps/backend/src/routes/invite-codes.ts


认证

接口分为两类:

  • 公开接口/validate/bind/settings):无需认证
  • 用户接口/my/*):需要 JWT 认证
Authorization: Bearer <JWT>

公开接口

1. 验证邀请码

POST /invite-codes/validate

验证邀请码是否可用(注册流程中调用,无需登录)。若邀请码功能关闭,直接返回通过。

请求体:

字段类型必填说明
codestring邀请码
emailstring注册邮箱

响应 200 OK(可用):

json
{
  "valid": true
}

响应 200 OK(功能关闭):

json
{
  "valid": true,
  "message": "Invite code not required"
}

响应 200 OK(同邮箱重复验证):

json
{
  "valid": true,
  "alreadyBound": true
}

响应 200 OK(不可用):

json
{
  "valid": false,
  "error": "INVALID_CODE"
}

error 错误码:

error说明
MISSING_PARAMS缺少 code 或 email
INVALID_CODE邀请码不存在
CODE_ALREADY_USED邀请码已被使用
CODE_EXPIRED邀请码已过期
EMAIL_ALREADY_REGISTERED邮箱已注册
INTERNAL_ERROR服务端错误

HTTP 错误码:

HTTP说明
400缺少参数
500服务端错误

2. 绑定邀请码

POST /invite-codes/bind

将邀请码绑定到邮箱(注册流程中调用,无需登录)。原子性操作,仅未使用且未过期的邀请码才能绑定。

请求体:

字段类型必填说明
codestring邀请码
emailstring注册邮箱

响应 200 OK(成功):

json
{
  "success": true
}

响应 200 OK(功能关闭):

json
{
  "success": true,
  "message": "Invite code not required"
}

错误码:

HTTPerror说明
400MISSING_PARAMS缺少 code 或 email
400BIND_FAILED绑定失败(码已使用或过期)
500INTERNAL_ERROR服务端错误

3. 获取邀请码配置

GET /invite-codes/settings

获取邀请码功能的公开配置信息。

响应 200 OK

json
{
  "enabled": true,
  "creditsPerCode": 1000
}
字段说明
enabled邀请码功能是否启用
creditsPerCode每个邀请码奖励积分数(用户可见单位)

错误码:

HTTP说明
500获取配置失败,返回 { "enabled": false }

用户接口(需 JWT 认证)

4. 获取我的邀请码列表

GET /invite-codes/my/codes

获取当前用户的所有邀请码(包括已使用的),按创建时间倒序。

used_by_display 是服务端生成的 { type, masked }typedisplay_name | nickname | email | phone | name | id | none,按此顺序降级。 可读名称取 profiles.display_name,微信昵称取 user_social_identities.nickname; 自动生成的邮箱前缀、微信名称、默认用户名称及 UUID 延后到联系方式之后。 邀请码历史邮箱为空、无效或为微信内部占位地址时,服务端只按当前列表关联的使用者 ID 补查 auth 账号,覆盖后绑定邮箱; 按 ID 去重,最多 5 个并发,不扫描全部账号。后绑定联系方式按数据库客户端隔离缓存 60 秒,每个客户端最多 2000 条;首次读取仍按缺失账号数查询。邮箱和手机保持服务端脱敏(包括填入名字字段的联系方式),微信占位邮箱不作为联系方式。 联系方式和名称均不可用时,id 返回该邀请码所关联使用者的短 ID(前 8 位 + 省略号 + 后 4 位),作为最后一项身份兜底;仅邀请码拥有者可见。 只有用户 ID 也缺失时才返回 none,前端显示「使用者信息不可用」,不显示 -

used_at 是已使用表的使用时间,expires_at 只用于未使用表。 表格占容器宽度 100%;未使用表不显示使用者列,已使用表限制使用者列宽为 280px,时间列 200px, 以省略号及 tooltip 展示长内容。直接显示名字,不添加来源前缀。

响应 200 OK

json
{
  "data": [
    {
      "id": "uuid",
      "code": "ABCD1234",
      "credits_milli": 1000000,
      "used_by_email": null,
      "used_by_display": { "type": "none", "masked": "-" },
      "used_at": null,
      "expires_at": "2026-04-06T00:00:00.000Z",
      "created_at": "2026-03-07T00:00:00.000Z"
    }
  ]
}

credits_milli 为毫积分单位(1 积分 = 1000 毫积分)。

错误码:

HTTP错误说明
401Unauthorized未认证
500Failed to fetch codes查询失败
500Internal server error服务端错误

5. 处理邀请码积分发放

POST /invite-codes/my/redeem

注册完成后处理邀请码积分发放。前端在用户首次登录后调用此接口:

  1. 查找绑定到该邮箱的邀请码
  2. 发放积分给邀请人和被邀请人
  3. 为新用户生成邀请码

响应 200 OK

json
{
  "success": true
}

错误码:

HTTP错误说明
401Unauthorized未认证或无邮箱信息
500Internal server error服务端错误

6. 生成邀请码

POST /invite-codes/my/generate

为当前用户生成邀请码,自动补齐到配置数量(默认每用户 5 个)。如果已有足够的邀请码,不会重复生成。

邀请码格式:8 位全大写字母 + 数字(排除易混淆字符 0OIL1),有效期默认 30 天。

响应 200 OK(生成了新码):

json
{
  "generated": 3
}

响应 200 OK(已有足够):

json
{
  "generated": 0,
  "message": "Already have enough codes"
}

错误码:

HTTP错误说明
401Unauthorized未认证
500Failed to generate codes生成失败
500Internal server error服务端错误

邀请人奖励(2026-06-10 起)

邀请码被成功使用(新用户完成注册关联)后,系统自动:

  1. 给邀请人发放奖励积分:额度由 site_settingsinvite.inviter_reward 配置 ({ "creditsMilli": 100000 },毫积分),未配置时默认 100 积分;设 0 关闭奖励。 流水 transaction_type = 'invite_bonus'metadata.role = 'inviter'(与被邀请人 的 invitee 区分)。
  2. 发送「邀请码被使用」站内通知:Novu workflow invite-code-used(需在 Novu 后台 配置模板,启动期 preflight 会校验),按邀请码 id 幂等,与奖励是否发放无关。

幂等保障:奖励与通知仅在邀请码 used_by_user_id 从空翻转为已关联时触发一次。

GET /invite-codes/my/activation

读取当前登录用户的邀请码激活状态。

POST /invite-codes/my/activate

为当前登录用户激活邀请码权益。请求体提供邀请码;重复激活或邀请码无效时返回对应业务错误。


额度与购买(需 JWT 认证)

免费额度用尽后,团队 Owner / 管理员可以用团队积分购买邀请码。价格、有效期与上限全部由站点配置 (site_settingsregistration.invite_code)在数据库事务内解析,接口入参不含价格。

GET /invite-codes/my/quota

额度概览与购买报价,前端据此决定按钮语义与是否显示购买入口。所有计数与 quoteVersion 由数据库 get_invite_purchase_context 计算,与购买 RPC 同一份口径。

查询参数:

参数类型必填说明
teamIdstring目标团队。传入时服务端先校验当前用户是否为该团队 Owner / 管理员;无权时不把 teamId 传给查询(团队余额保持 null),避免向任意登录用户泄漏团队余额

响应 200 OK

json
{
  "success": true,
  "data": {
    "freeQuotaTotal": 5,
    "freeQuotaUsed": 5,
    "freeQuotaRemaining": 0,
    "activeUnusedCount": 2,
    "purchase": {
      "enabled": true,
      "priceCredits": 100,
      "maxPerRequest": 20,
      "maxUnusedHolding": 50,
      "expirationDays": 30,
      "teamBalanceMilliCredits": 10000000
    },
    "quoteVersion": "v1|enabled=1|price=100|maxReq=20|maxHold=50|expDays=30|freeQuota=5",
    "canIssueUnlimited": false
  }
}
字段说明
freeQuotaTotal / freeQuotaUsed / freeQuotaRemaining免费额度(终身口径,按 source='free' 计数);剩余数下限为 0
activeUnusedCount当前有效且未使用的邀请码数量(含购买的码,为持有上限口径)
purchase购买配置快照;enabled、单价、单次上限、持有上限、有效期由站点配置决定,未配置时数值字段为 nullenabledfalse
purchase.teamBalanceMilliCredits目标团队积分余额(毫积分);未传 teamId 或无购买权时为 null
quoteVersion报价版本指纹;购买时须原样回传
canIssueUnlimited当前用户是否持有 site:invite_codes 站点权限(可无限发码)

错误码:

HTTP错误说明
401Unauthorized未认证
500config_invalid站点配置非法(响应附 field 字段指出问题配置)
500Internal server error服务端错误

POST /invite-codes/my/purchase

用团队积分购买邀请码。仅团队 Owner / 管理员可调用——团队积分是共享资产,只有管理角色能把它换成 归个人所有的邀请码。

入参不含价格:价格与有效期由 RPC 在事务内从锁定的配置解析,是唯一权威来源。前端把 GET /my/quota 拿到的 quoteVersion 原样回传,RPC 事务内再比对一次,避免「用户确认的是旧价、 扣款按新价」。

请求体:

字段类型必填说明
teamIdstring扣款的团队
quantitynumber购买数量(正整数,且不超过配置的单次上限)
requestIdstring幂等键(与 userId 组合成数据库幂等键,不含 teamId/quantity;重复请求返回同一结果)
quoteVersionstringGET /my/quota 返回的报价版本
json
{
  "teamId": "团队 UUID",
  "quantity": 5,
  "requestId": "客户端生成的唯一请求 ID",
  "quoteVersion": "v1|enabled=1|price=100|maxReq=20|maxHold=50|expDays=30|freeQuota=5"
}

响应 200 OK

json
{
  "success": true,
  "data": {
    "codes": ["ABCD2345", "EFGH6789"],
    "transactionId": "流水 UUID",
    "balanceAfterMilli": 9950000,
    "unitPriceMilli": 100000,
    "totalMilli": 500000,
    "expiresAt": "2026-09-28T00:00:00.000Z",
    "idempotent": false
  }
}
字段说明
codes新购买的邀请码明文
balanceAfterMilli扣款后团队余额(毫积分)
unitPriceMilli / totalMilli单价与总价(毫积分)
expiresAt本批邀请码过期时间(按配置的有效期天数计算)
idempotent命中幂等重放时为 true(不重复扣费)

错误码:

HTTPerror说明
400MISSING_PARAMS缺少 teamId / requestId / quoteVersion
400invalid_quantityquantity 非正整数
400purchase_disabled站点未开启购买
400quantity_exceeds_limit超过单次购买上限
400holding_limit_exceeded超过持有上限(活跃未用码过多)
400free_quota_remaining免费额度尚未用完,不允许购买
402insufficient_credits团队积分余额不足(充值后可重试)
403forbidden当前用户不是该团队 Owner / 管理员
409quote_stale报价已过期,需重新拉取 quota 后确认
409idempotency_key_conflict幂等键重复但参数(数量等)不一致
409activation_not_atomic邀请码功能尚未切换到原子激活路径
500config_invalid / balance_inconsistent / balance_not_found / internal_error平台侧问题
503code_collision生成邀请码撞码,事务已整体回滚,可直接重试

失败响应带 detail 对象,按错误类型包含 reasonbalancerequiredfree_remainingactive_unusedmax_holding 等上下文字段。

AI Workflow Editor