← 返回账户中心

统一账户接入文档

# 落锦统一账户接口 v1

账户中心:https://luo-jin-ai.com/luojin-account/

接口地址:`https://luo-jin-ai.com/api/unified/v1`

账户身份、余额 `users.quota`、积分、交易流水、应用共享数据和王从天降解锁权限统一存放于 NewAPI 数据库。历史业务记录通过稳定的旧业务用户 ID 对应 NewAPI 用户 ID;已有文档、会话等业务表保留原有业务存储。禁止业务系统维护第二份可修改的余额或积分。

## 金额与积分

- **1 元人民币余额兑换 100 积分**。兑换最小 0.01 元,对应 1 积分。
- 充值只增加 NewAPI 余额;兑换时扣余额、加积分,不会充值同时赠送同额积分。
- 公务及王从天降消耗积分。中转站模型调用消耗余额。
- NewAPI 内部 500000 quota 为 1 美元,人民币显示使用 `/api/status` 返回的 `usd_exchange_rate`。当前配置为 7.3。不要把 500000 quota 当作 1 元人民币。
- 兑换扣除 quota 向上取整,避免超发;展示余额保留小数,底层结算使用整数 quota。精度差异小于一个 quota。
- 充值商店在账户中心弹窗中打开;支付后使用 NewAPI 兑换码或 NewAPI 在线充值。支付订单状态以 NewAPI 为准。

## 身份与权限

用户请求带 `Authorization: Bearer <用户访问令牌>`。支持统一账户登录返回的 token,以及 NewAPI 的用户访问 token(不是模型调用 API key)。浏览器使用本站 HttpOnly 会话亦可。令牌、密码和应用密钥不得进入 URL、日志或前端代码仓库。

第三方系统的扣费、退款和数据接口额外要求 `X-App-Key: <应用服务端密钥>`。每个应用仅能读写自己的命名空间,不能通过提交 userId 冒用其他用户;用户身份始终由用户令牌确定。应用密钥单独使用不能扣费。

服务器管理员通过以下命令创建凭证,密钥只写入指定的私有文件:

```sh
cd /www/wwwroot/ai-chat-web
node unified-client.cjs create example /root/.config/luojin/example.json data:read,data:write,points:consume,points:refund
node unified-client.cjs revoke example
```

默认仅提供数据读写权限;需要扣费时明确加入积分权限。不要把应用密钥放进浏览器。

## 接口

| 方法 | 路径 | 用途 |
|---|---|---|
| GET | `/config` | 公开充值地址与兑换规则 |
| GET | `/me` | 当前用户身份、余额、积分、会员信息 |
| GET | `/transactions?page=1` | 统一账户交易流水,每页 20 条 |
| POST | `/exchange` | 人民币余额兑换积分 |
| POST | `/redeem` | 调用 NewAPI 兑换码系统充值 |
| GET | `/entitlements` | 王从天降解锁项目、单项目价格与整包价格 |
| POST | `/apps/consume` | 应用扣除积分,需 `points:consume` |
| POST | `/apps/refund` | 原扣费退款,需 `points:refund` |
| GET | `/apps/data/{key}` | 读取本应用当前用户数据,需 `data:read` |
| PUT | `/apps/data/{key}` | 有版本校验地写入数据,需 `data:write` |

注册和登录继续使用 `POST /api/auth/register`、`POST /api/auth/login`,JSON 为 `username`、`password`。密码遵循 NewAPI 的规则。已存在的同名账户不会自动合并;`POST /api/auth/link` 需同时验证旧账户与 NewAPI 账户密码。

### 查询账户

```sh
curl https://luo-jin-ai.com/api/unified/v1/me \
  -H "Authorization: Bearer $USER_TOKEN"
```

返回主要字段:`userId` / `newapiUserId`(NewAPI ID)、`username`、`balanceYuan`、`balanceCents`、`quota`、`points`、`pointsPerYuan`、`membership`。不要用显示字段在调用方自行扣费。

### 兑换 1 元为 100 积分

```json
{"amountYuan":"1.00"}
```

请求:`POST /exchange`,同时带 `Idempotency-Key: exchange-your-order-001`。也可提交整数 `amountCents: 100`。余额不足返回 402,整个事务回滚。

### 应用扣费与退款

```json
{"points":10,"reference":"business-order-001","description":"报告生成"}
```

请求:`POST /apps/consume`,带用户令牌、`X-App-Key` 和 `Idempotency-Key`。返回 `transaction.id` 与最新账户。

失败退款:`POST /apps/refund`,请求 `{"transactionId":"原transaction.id","reason":"生成失败"}`。只能退当前用户在当前应用的原扣费,重复退款不会重复增加积分。

**重试约定:** 为每个业务动作生成稳定请求编号;超时后使用原编号重试。同一用户、应用、请求编号仅扣费一次;相同编号对应不同参数返回 409。退款也以原交易编号防重。幂等保护的是账本,接入方仍需缓存业务结果,避免重复执行自己的任务。

### 数据交互

初次 `PUT /apps/data/preferences`:

```json
{"version":0,"value":{"theme":"light","language":"zh-CN"}}
```

返回 `version:1`。之后更新必须传当前版本,冲突返回 409,重新读取后合并。每条数据最多 64KB;键名为 1–80 位字母、数字、下划线、点或横线。不同用户及应用完全隔离。

## 常见返回码

400 参数错误;401 登录过期或账户停用;402 余额/积分不足;403 应用无权限;404 数据不存在;409 请求冲突/版本冲突/需关联账户;410 旧财务入口已停用;413 数据过大;429 请求过频;503 NewAPI 暂不可用。服务不可用时不会把余额错误显示为零。

## 迁移与运维

迁移使用 `lj_migrations` 记录来源,重复执行不会重复加钱。两笔已确认的测试账户资产标记 `excluded_test`。同名但未验证归属的账户标记 `pending_identity`,原记录保留,需验证后关联。旧支付订单与历史流水保留供核对;旧支付回调、手工入账、兑换码发行及旧钱包写入入口已停用。

NewAPI 管理充值、兑换码和模型调用流水;统一账户中心显示积分、兑换及经账户中心进行的兑换码流水。原生 NewAPI 的其他充值与消费在中转站明细查看,双方余额来自同一 `users.quota`。

`node test-unified.cjs` 运行独立临时库的并发、幂等、退款、权限隔离和版本冲突测试,不修改生产资金。

线上发布前必须备份 NewAPI SQLite 和旧 MySQL 数据。代码可回退;产生新交易后不能直接覆盖数据库备份,否则会丢失发布后的交易。先停止写入并逐笔对账后再决定数据恢复。