@seaverse/assets-sdk
v0.0.2
Published
SeaVerse Assets API SDK - Unified multi-tenant payment and credit management service
Downloads
210
Readme
@seaverse/assets-sdk
SeaVerse Asset Hub SDK - 统一的多租户积分管理服务(4 池系统)
概述
Asset Hub SDK 提供了完整的积分管理功能,支持 4 池积分系统(daily/event/monthly/permanent)和传统的积分账户查询。所有应用程序(包括 SeaVerse 平台本身)都使用相同的 SDK 路由和多租户架构。
主要 API 端点:
/sdk/v1/credits/detail- 获取 4 池积分详情(推荐)/sdk/v1/credits/account- 获取积分账户信息(已弃用)/sdk/v1/credits/transactions- 查询交易历史
核心概念
应用 ID
每个应用程序都有唯一的 app_id:
- SeaVerse 平台:
appId = "seaverse"(官方平台应用,如 ops-frontend、meta-product) - 第三方应用:
appId = "game-abc123"、appId = "social-xyz789"等
多租户隔离
- 积分账户按
app_id隔离 - 每个应用都有自己的积分池和交易历史
- 同一用户在不同应用中可以有不同的积分账户
安装
npm install @seaverse/assets-sdk
# 或
pnpm add @seaverse/assets-sdk功能特性
- 4 池积分系统: 支持 daily/event/monthly/permanent 四种积分池
- 过期时间追踪: 每个积分池都有独立的过期时间戳
- 消费优先级: Daily → Event → Monthly → Permanent
- 查询用户积分账户信息,支持多租户
- 列出交易历史,支持筛选和分页
- TypeScript 支持,包含完整类型定义
- 只读 API(积分消费/发放由平台后端处理)
快速开始
import { PaymentClient } from '@seaverse/assets-sdk';
// 为 SeaVerse 平台初始化客户端
const client = new PaymentClient({
appId: 'seaverse',
token: 'your-bearer-token'
});
// 或为第三方应用初始化
const gameClient = new PaymentClient({
appId: 'game-abc123',
token: 'your-bearer-token'
});
// 获取积分详情(推荐使用 - 4 池系统)
const detail = await client.getCreditDetail();
console.log(`总余额: ${detail.total_balance} 积分`);
// 显示各个积分池的详情
detail.pools.forEach(pool => {
const expiryLabel = pool.expires_at === 0
? '永久有效'
: `${Math.floor((pool.expires_at - Date.now()) / 86400000)} 天后过期`;
console.log(`${pool.type}: ${pool.balance} (${expiryLabel})`);
});
// 列出最近的交易
const result = await client.listTransactions({ page_size: 10 });
console.log(`找到 ${result.transactions.length} 笔交易`);
console.log(`有更多: ${result.has_more}`);4 池积分系统
Asset Hub SDK 使用 4 池积分系统,每种池有不同的过期规则和用途:
积分池类型
| 池类型 | 说明 | 过期规则 |
|--------|------|----------|
| daily | 每日积分 | 次日 00:00 UTC 过期 |
| event | 活动积分 | 活动截止时间过期 |
| monthly | 每月积分 | 最后发放时间 + 30天过期 |
| permanent | 通用积分 | 永久有效 |
消费优先级
当用户消费积分时,系统按以下优先级扣除:
Daily (每日) → Event (活动) → Monthly (每月) → Permanent (通用)这确保先消耗即将过期的积分,最大化用户的积分价值。
使用示例
const detail = await client.getCreditDetail();
// 计算总余额
console.log(`总计: ${detail.total_balance} 积分`);
// 遍历各个积分池
for (const pool of detail.pools) {
const remaining = pool.expires_at === 0
? Infinity
: pool.expires_at - Date.now();
if (remaining === Infinity) {
console.log(`${pool.type}: ${pool.balance} 积分 (永久有效)`);
} else {
const days = Math.floor(remaining / 86400000);
const hours = Math.floor((remaining % 86400000) / 3600000);
console.log(`${pool.type}: ${pool.balance} 积分 (${days}天${hours}小时后过期)`);
}
}
// 检查是否有即将过期的积分
const expiringPools = detail.pools.filter(pool => {
if (pool.expires_at === 0) return false;
const daysLeft = (pool.expires_at - Date.now()) / 86400000;
return daysLeft < 3; // 3天内过期
});
if (expiringPools.length > 0) {
console.log('⚠️ 有积分即将过期,请尽快使用!');
expiringPools.forEach(pool => {
console.log(` - ${pool.type}: ${pool.balance} 积分`);
});
}API 参考
PaymentClient
构造函数
new PaymentClient(options: PaymentClientOptions)参数:
appId(string, 必需): 用于多租户隔离的应用 ID'seaverse'用于 SeaVerse 平台'game-abc123'用于第三方应用
token(string, 必需): 用于身份验证的 Bearer tokenbaseURL(string, 可选): Asset Hub API 的基础 URL- 默认:
'https://payment.sg.seaverse.dev' - 生产环境:
'https://asset.sg.seaverse.dev'
- 默认:
timeout(number, 可选): 请求超时时间(毫秒),默认 30000
方法
getCreditDetail()
获取积分详情(4 池系统)- 推荐使用
返回包含 4 种积分池的详细信息,每个池都有余额和过期时间戳。
API 端点: GET /sdk/v1/credits/detail
多租户行为:
- 积分池属于
appId指定的应用 - 每个应用都有独立的 4 池系统
- 池数据按
app_id隔离
请求头:
Authorization: Bearer <token>- 必需X-App-ID: <app_id>- 必需
async getCreditDetail(): Promise<CreditDetailResponse>返回:
{
total_balance: string // 所有池的总余额(字符串保证精度)
pools: [ // 积分池数组(仅包含余额 > 0 的池)
{
type: 'daily' | 'event' | 'monthly' | 'permanent'
balance: string // 该池的余额(字符串保证精度)
expires_at: number // 过期时间戳(毫秒)
// 0 = 永久有效
// > 0 = 具体过期时间
}
]
}积分池类型:
daily- 每日积分(次日 00:00 UTC 过期)event- 活动积分(活动截止时间过期)monthly- 每月积分(最后发放时间 + 30天过期)permanent- 通用积分(永久有效)
响应特点:
- 仅返回
balance > 0的积分池 expires_at为毫秒时间戳(0 表示永久有效)- 前端可根据时间戳计算剩余时间
建议刷新频率: 30 秒
示例:
const detail = await client.getCreditDetail();
console.log(`总余额: ${detail.total_balance}`);
detail.pools.forEach(pool => {
const label = pool.expires_at === 0
? '永久有效'
: `${Math.floor((pool.expires_at - Date.now()) / 86400000)} 天后过期`;
console.log(`${pool.type}: ${pool.balance} (${label})`);
});
// 输出示例:
// 总余额: 5240
// daily: 150 (0 天后过期)
// event: 500 (5 天后过期)
// monthly: 800 (28 天后过期)
// permanent: 3790 (永久有效)getCreditAccount()
⚠️ 已弃用: 推荐使用
getCreditDetail()来获取新的 4 池积分系统信息。
获取已认证用户在指定应用中的积分账户信息。
API 端点: GET /sdk/v1/credits/account
多租户行为:
- 积分账户属于
appId指定的应用 - 每个应用都有隔离的积分池
- 账户数据按
app_id隔离
请求头:
Authorization: Bearer <token>- 必需X-App-ID: <app_id>- 必需
async getCreditAccount(): Promise<CreditAccount>返回:
{
id: string
user_id: string
app_id: string | null // 应用 ID (平台用户为 null)
balance: number // 当前可用余额
total_earned: number // 累计获得
total_spent: number // 累计消费
frozen_balance: number // 冻结余额(不可用)
last_transaction_id?: string
status: 'active' | 'suspended' | 'frozen'
status_reason?: string // 仅在非活跃状态时存在
created_at: string // UTC 时间戳
updated_at: string // UTC 时间戳
last_activity_at?: string // UTC 时间戳
}账户状态:
active- 正常,可用suspended- 已暂停(请联系支持)frozen- 已冻结(风险控制)
注意事项:
- 某些字段可能为
null或undefined,请做好空值处理 - 数字字段可能以字符串形式返回,SDK 会自动处理类型转换
- 建议使用可选链操作符
?.访问可选字段
示例:
const account = await client.getCreditAccount();
console.log(`余额: ${account.balance ?? 0}`);
console.log(`应用: ${account.app_id ?? 'N/A'}`);
console.log(`状态: ${account.status}`);
// 安全访问可选字段
if (account.last_activity_at) {
console.log(`最后活动: ${account.last_activity_at}`);
}listTransactions()
列出指定应用的积分交易,支持筛选和分页。
API 端点: GET /sdk/v1/credits/transactions
多租户行为:
- 交易按
appId指定的应用筛选 - 仅返回已认证用户在此应用中的交易
- 交易历史按
app_id隔离
请求头:
Authorization: Bearer <token>- 必需X-App-ID: <app_id>- 必需
async listTransactions(request?: ListTransactionsRequest): Promise<TransactionListResponse>请求参数:
type(可选): 按交易类型筛选'earn'- 积分获得'spend'- 积分扣除'freeze'- 冻结积分'unfreeze'- 解冻积分'refund'- 退款返还'adjust'- 管理员调整
source(可选): 按交易来源筛选(例如'api_call'、'purchase')status(可选): 按状态筛选'pending'- 处理中'completed'- 已完成'failed'- 失败'cancelled'- 已取消
start_date(可选): 开始日期筛选(RFC3339 格式,例如'2024-01-01T00:00:00Z')end_date(可选): 结束日期筛选(RFC3339 格式,例如'2024-12-31T23:59:59Z')page(可选): 页码(从 1 开始,默认: 1)page_size(可选): 每页条数(最小: 1,最大: 50,默认: 20)
返回:
{
transactions: CreditTransaction[]
page: number
page_size: number
has_more: boolean
}CreditTransaction:
{
id: string
user_id: string
app_id: string | null // 应用 ID (平台用户为 null)
type: 'spend' | 'earn' | 'refund' | 'adjust' | 'freeze' | 'unfreeze'
amount: number // 负数表示扣除,正数表示增加
balance_before: number
balance_after: number
source: string // 交易来源标识符
source_id?: string // 关联的来源 ID(订单/任务/请求 ID)
description: string
status: 'pending' | 'completed' | 'failed' | 'cancelled'
status_reason?: string // 仅在非已完成状态时存在
metadata?: Record<string, any> // 自定义元数据
created_at: string // UTC 时间戳
completed_at?: string // UTC 时间戳
}示例:
// 获取最近的交易
const result = await client.listTransactions({ page_size: 20 });
// 按类型筛选
const spending = await client.listTransactions({
type: 'spend',
page: 1,
page_size: 10
});
// 按日期范围筛选
const recent = await client.listTransactions({
start_date: '2024-01-01T00:00:00Z',
end_date: '2024-12-31T23:59:59Z',
status: 'completed'
});
// 分页
const page2 = await client.listTransactions({
page: 2,
page_size: 20
});
if (page2.has_more) {
// 有更多结果可用
}setToken()
更新用于身份验证的 bearer token。
当 token 过期或需要刷新时使用此方法。
setToken(token: string): void示例:
client.setToken('new-bearer-token');setBaseURL()
更新 API 的基础 URL。
使用此方法在不同环境(开发/测试/生产)之间切换。
setBaseURL(baseURL: string): void示例:
client.setBaseURL('https://payment.sg.seaverse.dev');setAppId()
更新用于多租户隔离的应用 ID。
使用此方法在运行时在不同应用之间切换。
setAppId(appId: string): void示例:
// 切换到 SeaVerse 平台
client.setAppId('seaverse');
// 切换到第三方应用
client.setAppId('game-abc123');错误处理
SDK 会为 API 错误抛出 PaymentAPIError:
import { PaymentAPIError } from '@seaverse/assets-sdk';
try {
const account = await client.getCreditAccount();
} catch (error) {
if (error instanceof PaymentAPIError) {
console.error(`API 错误 [${error.code}]: ${error.message}`);
console.error('响应:', error.response);
} else {
console.error('意外错误:', error);
}
}常见错误码:
400- 错误的请求(无效参数)401- 未授权(无效或过期的 token)403- 禁止访问500- 内部服务器错误
环境配置
不同环境有不同的基础 URL:
// 开发/测试环境(默认)
const client = new PaymentClient({
appId: 'seaverse',
token: devToken
// baseURL 默认为 'https://payment.sg.seaverse.dev'
});
// 生产环境(推荐)
const client = new PaymentClient({
appId: 'seaverse',
baseURL: 'https://asset.sg.seaverse.dev',
token: prodToken
});
// 本地开发
const client = new PaymentClient({
appId: 'seaverse',
baseURL: 'http://localhost:8080',
token: localToken
});环境说明:
- 开发/测试:
https://payment.sg.seaverse.dev(SDK 默认) - 生产:
https://asset.sg.seaverse.dev(推荐用于生产环境) - 本地:
http://localhost:8080(本地调试)
多租户使用示例
SeaVerse 平台应用
// 官方 SeaVerse 平台 (ops-frontend、meta-product 等)
const platformClient = new PaymentClient({
appId: 'seaverse',
token: userToken
});
const account = await platformClient.getCreditAccount();
console.log(`平台余额: ${account.balance}`);第三方游戏应用
// 拥有自己积分池的第三方游戏
const gameClient = new PaymentClient({
appId: 'game-abc123',
token: userToken
});
const gameAccount = await gameClient.getCreditAccount();
console.log(`游戏余额: ${gameAccount.balance}`);
// 同一用户,不同应用 - 隔离的积分账户
// gameAccount.balance 可能与 platformClient 的余额不同在应用之间切换
const client = new PaymentClient({
appId: 'seaverse',
token: userToken
});
// 查询 SeaVerse 平台账户
const platformAccount = await client.getCreditAccount();
// 切换到游戏应用
client.setAppId('game-abc123');
// 查询同一用户的游戏账户
const gameAccount = await client.getCreditAccount();
// 两个账户都属于同一用户,但是隔离的积分单位
- 1 积分 = 1 单位
- 所有金额都表示为整数(无小数)
身份验证
- Token 通过 SeaVerse auth-service 获取
Authorizationheader 中需要 Bearer token- 默认 token 有效期: 10 天
- 所需范围:
account
安全性
域名白名单(推荐)
对于生产应用,注册允许的域名以防止未经授权的使用:
Originheader 会根据应用的白名单进行验证- 防止
app_id被盗用和滥用
速率限制
- 按
app_id应用速率限制 - 防止滥用
故障排除
常见问题
1. Cannot read properties of undefined 错误
如果遇到类似 Cannot read properties of undefined (reading 'toLocaleString') 的错误,说明 API 返回的某些字段为 undefined 或 null。
解决方案:
// ✅ 推荐:使用空值合并运算符
const balance = account.balance ?? 0;
const totalEarned = account.total_earned ?? 0;
// ✅ 推荐:使用可选链
const lastActivity = account.last_activity_at ?? 'N/A';
// ✅ 推荐:显示前检查值是否存在
if (account.balance !== null && account.balance !== undefined) {
console.log(`余额: ${account.balance.toLocaleString()}`);
}2. CORS 错误
如果在浏览器中遇到 CORS 错误,请确保:
- 你的域名已添加到应用的白名单中
X-App-IDheader 正确设置- 使用正确的 baseURL
3. 401 未授权错误
可能原因:
- Token 已过期(默认有效期 10 天)
- Token 格式不正确
- Token 没有正确的 scope(需要
accountscope)
解决方案:
// 重新获取 token
const newToken = await authService.getToken();
client.setToken(newToken);4. 数据类型不匹配
API 可能返回字符串格式的数字,请确保你的代码能够处理:
// ✅ 安全的类型转换
const balance = typeof account.balance === 'string'
? parseFloat(account.balance)
: account.balance;
// ✅ 使用 SDK 提供的类型检查
const formatNumber = (value: string | number | null | undefined): number => {
if (value === null || value === undefined) return 0;
const num = typeof value === 'string' ? parseFloat(value) : value;
return isNaN(num) ? 0 : num;
};许可证
MIT
