VIO 对外 API 参考(External API)
本文档说明如何通过 API Key 调用 VIO 对外接口;文中路径、请求与响应字段名与正式环境一致。
目录
1. 概述(Overview)
什么是 VIO External API?
VIO External API 供租户以编程方式将自有系统与 VIO 平台对接。可通过本 API:
- 管理礼券(创建、更新、删除、核销)
- 管理用户及其数据
- 创建并管理礼券活动
- 处理代币、余额与交易
- 获取分析与报表数据
基础 URL(Base URL)
https://{your-domain}/api/external/v1所有接口路径均相对于上述基础 URL。
API 版本(Versioning)
当前 API 发布版本为 1.1.0,URL 路径版本保持为 v1。当出现破坏性变更时将发布新的 URL 路径版本,并尽可能保持旧版本可用。
架构示意(Architecture)
┌─────────────────────────────────────────────────────────────────┐
│ 你的应用 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 携带 X-API-Key 请求头 │ │
│ │ 2. 向 /api/external/v1/* 发起 HTTPS 请求 │ │
│ │ 3. 解析 JSON 响应 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ VIO API 服务 │
│ /api/external/v1 │
│ │
│ • 鉴权 │
│ • 限流 │
│ • Scope 校验 │
│ • 请求处理 │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 数据库 │
│ (租户数据) │
└─────────────────────┘2. 鉴权(Authentication)
API Key
所有请求须使用 API Key 鉴权。API Key 绑定租户,可配置不同权限范围(scope)。
如何获取 API Key
- 登录 VIO 管理后台(Admin Portal)
- 进入 设置 > API Keys
- 点击 创建 API Key
- 为该 Key 选择 scope(权限)
- 可选:配置 IP 白名单
- 复制并安全保存生成的 Key
重要:API Key 仅在创建时完整展示一次,请务必妥善保管。
在请求中使用 API Key
每个请求在请求头中携带 X-API-Key:
curl -X GET "https://your-domain.com/api/external/v1/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here"API Key 格式
- 生产:
vio_live_xxxxxxxxxxxxxxxx - 测试:
vio_test_xxxxxxxxxxxxxxxx
Scope(权限范围)
每个 API Key 可配置一个或多个 scope,决定可访问的接口:
| Scope | 说明 | 路径前缀 |
|---|---|---|
vouchers | 礼券与领取记录 | /vouchers/* |
users | 用户 | /users/* |
campaigns | 活动 | /campaigns/* |
tokens | 代币与余额 | /tokens/* |
analytics | 分析数据 | /analytics/* |
IP 白名单
为加强安全,可将 API Key 限制在指定 IP:
- 打开 管理后台 > 设置 > API Keys
- 编辑对应 API Key
- 添加允许的 IP 或 CIDR
- 保存
非白名单 IP 请求将返回 403 Forbidden。
3. 限流(Rate Limiting)
默认限额
为保证公平与稳定,接口实行限流:
| 限额类型 | 默认值 |
|---|---|
| 每分钟 | 60 次请求 |
| 每天 | 10,000 次 |
限流响应头
响应中会包含限流相关信息:
| Header | 说明 |
|---|---|
X-RateLimit-Limit | 当前时间窗口内上限 |
X-RateLimit-Remaining | 当前窗口剩余次数 |
X-RateLimit-Reset | 限额重置的 Unix 时间戳 |
响应头示例
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1699574400超出限流
超限将返回 429 Too Many Requests:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 60 seconds."
}
}响应通常包含 Retry-After,提示多久后可重试。
最佳实践
- 收到
429时使用指数退避重试 - 在合适场景下缓存响应
- 尽量批量操作
- 根据响应头监控用量
4. 请求与响应格式(Request & Response)
请求格式
- 请求体须为 JSON
- POST/PATCH 须带
Content-Type: application/json - 过滤与分页使用查询参数
成功响应
成功时结构如下:
{
"success": true,
"data": { ... },
"message": "Optional success message"
}分页列表
列表类接口返回分页结构:
{
"success": true,
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNextPage": true,
"hasPrevPage": false
}
}分页参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
page | integer | 1 | 页码(从 1 开始) |
limit | integer | 20 | 每页条数(最大 100) |
错误响应
错误时结构如下:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description"
}
}5. 错误处理(Error Handling)
HTTP 状态码
| Status Code | 含义 |
|---|---|
200 OK | 成功 |
201 Created | 创建成功 |
400 Bad Request | 参数或 body 无效 |
401 Unauthorized | 缺少或无效的 API Key |
403 Forbidden | Scope 不足或未通过 IP 白名单 |
404 Not Found | 资源不存在 |
429 Too Many Requests | 超出限流 |
500 Internal Server Error | 服务器错误 |
业务错误码(Error Codes)
| Error Code | 说明 |
|---|---|
VALIDATION_ERROR | 校验失败 |
UNAUTHORIZED | API Key 缺失或无效 |
FORBIDDEN | 无所需 scope |
NOT_FOUND | 资源不存在 |
RATE_LIMIT_EXCEEDED | 请求过于频繁 |
ALREADY_EXISTS | 资源已存在(如重复用户) |
INSUFFICIENT_BALANCE | 代币余额不足 |
ALREADY_REDEEMED | 礼券已核销 |
EXPIRED | 礼券或代币已过期 |
INTERNAL_ERROR | 未预期的服务器错误 |
错误响应示例
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
}6. API 参考:礼券(Vouchers)
说明: 本章中 HTTP 路径、方法、JSON 字段名及 cURL/代码块与线上实现一致;说明性文字为中文。
所需 Scope: vouchers
领取与核销流程(重要)
以下为两类不同概念,请勿混淆:
- 礼券模板(
Voucher):租户在平台中创建或与外部同步的礼券定义。 - 用户礼券 / 领取记录(
UserVoucher):用户经「领取」或「发放(send)」后得到的单用户实例。
按场景选择流程
若仅通过 API 对接,请按下表选用路径:
| 场景 | 第 1 步(发放/领取) | 第 2 步(核销) | 典型用途 |
|---|---|---|---|
| 用户从活动自助领取 | POST /api/vouchers/:voucherId/claim 且带 campaignId | POST /api/vouchers/me/:userVoucherId/redeem | 会员端应用(JWT) |
| 租户/后端直接向用户发券 | POST /vouchers/:voucherId/send 且带 userId | POST /vouchers/redeem-by-code 或 POST /vouchers/redeem/:code/pin | 服务端 API Key 对接 |
租户自管礼券与外部供货礼券的阶段顺序相同:均为先发券(领取或 send)→ 再核销。差异在核销阶段:
- 租户自管礼券:核销将领取记录标记为已使用,不调用外部供货系统。若
consumptionType为qr_code或coupon_code且配置了 CSV 码池(管理端上传),核销时会从池中原子分配下一条码至externalVoucherCode,再标记已使用;端侧应在此时再展示供应商码 / 由该码生成的 QR 或条码。 - 外部供货礼券:核销会触发供货方履约(如下单)。核销后须根据响应中的
externalFulfillmentStatus确认是否成功。
POST /vouchers/:voucherId/send 在 External API 中等价于「为指定用户完成领取」:会创建 UserVoucher 并返回 redemptionCode。
类型判断规则
依据礼券模板字段区分来源与用法:
externalProvider与externalId均存在:为外部供货礼券。externalProvider与externalId均缺失:为租户自管礼券。consumptionType表示核销交互方式(vio_code、coupon_code、url、qr_code、manual、zhichong),不能单独用作区分「外部 / 租户自管」的依据。租户自管的qr_code/coupon_code可与管理端 CSV 码池 配合:每次会员 / 店员 / API 核销成功时从池中取一条唯一码写入externalVoucherCode;模板 领取上限 不得超过池中码总数(见 Admin 用户指南)。voucherType为业务分类字段,不能单独用作区分「外部 / 租户自管」的依据。
应在哪些接口读取判断信息
| 需要判断的内容 | 使用的接口 | 关键字段 |
|---|---|---|
| 外部供货还是租户自管? | GET /vouchers 或 GET /vouchers/:voucherId | externalProvider、externalId |
| 核销时需要哪种输入方式? | GET /vouchers 或 GET /vouchers/:voucherId | consumptionType、externalRequiresDirectOrderParams |
| 应对哪一条领取记录核销? | 领取/send 的响应或用户领取列表 | userVoucherId(_id)、redemptionCode、status |
| 外部履约结果? | 核销响应 | externalVoucherCode、externalRedemptionUrl、externalOrderId、externalFulfillmentStatus |
GET /vouchers/redeem/:code/info 仅用于展示,不要作为判断礼券类型的数据源。
领取 / 核销示例
A) 使用 External API(API Key)向指定用户发放(send)
适用于由贵方后端直接向用户派券的场景。
curl -X POST "https://your-domain.com/api/external/v1/vouchers/{voucherId}/send" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015"
}'成功响应示例(关键字段):
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"voucherId": "507f1f77bcf86cd799439011",
"userId": "507f1f77bcf86cd799439015",
"status": "active",
"redemptionCode": "VCH-M1ABC2-XY3Z"
},
"message": "Voucher sent to user"
}B) 使用 External API(API Key)按核销码核销
适用于非 zhichong 的服务端对接或门店收银等。
curl -X POST "https://your-domain.com/api/external/v1/vouchers/redeem-by-code" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"redemptionCode": "VCH-M1ABC2-XY3Z",
"location": "Store #42",
"notes": "POS order #12345"
}'成功响应示例(关键字段):
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"status": "redeemed",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"redeemedAt": "2024-02-01T10:15:00.000Z",
"externalVoucherCode": "ABC-123-XYZ",
"externalRedemptionUrl": null,
"externalOrderId": "69f75fe31134f32e472b2f98",
"externalFulfillmentStatus": "fulfilled"
},
"message": "Voucher redeemed"
}C) zhichong 直充核销(会员 API,JWT)
当 consumptionType 为 zhichong 时,须调用会员侧核销接口,并在 body 中传入 providerParams(含充值账号等):
curl -X POST "https://your-domain.com/api/vouchers/me/{userVoucherId}/redeem" \
-H "Authorization: Bearer {member_jwt}" \
-H "Content-Type: application/json" \
-d '{
"providerParams": {
"account": "12312332123"
}
}'成功响应示例(关键字段):
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"status": "redeemed",
"externalFulfillmentStatus": "fulfilled",
"externalIsDirectRecharge": true,
"externalRechargeAccount": "12312332123",
"externalOrderId": "69f75fe31134f32e472b2f98"
},
"message": "Voucher redeemed"
}礼券来源与类型对照
| 来源 | externalProvider | externalId | 常见 consumptionType | 建议领取/发放 | 建议核销 |
|---|---|---|---|---|---|
| 租户自管 | 无 | 无 | vio_code、coupon_code、url、qr_code、manual | POST /vouchers/:voucherId/send(API Key)或会员领取流程 | POST /vouchers/redeem-by-code、POST /vouchers/redeem/:code/pin 或会员侧核销 |
| 外部供货(码/链接类) | 有 | 有 | 多为 coupon_code 或 url | 须先领取/send | 领取/send 后再核销 |
外部供货(zhichong) | 有 | 有 | zhichong | 须先领取/send | POST /api/vouchers/me/:userVoucherId/redeem 且带 providerParams.account |
POST /api/external/v1/vouchers/redeem-by-code不接受providerParams。POST /api/external/v1/vouchers/redeem/:code/pin不能用于zhichong类型。
错误处理与履约状态
常见发放(send/claim)错误
| HTTP 状态 | 错误码 | 原因 |
|---|---|---|
| 404 | NOT_FOUND | voucherId 或 userId 不存在 |
| 400 | VALIDATION_ERROR | 礼券未启用或已过期 |
| 400 | VALIDATION_ERROR | 已抢完(claimedQuantity >= totalQuantity) |
| 400 | VALIDATION_ERROR | 用户已达 maxClaimsPerUser 上限 |
常见核销错误
| HTTP 状态 | 错误码 | 原因 |
|---|---|---|
| 404 | NOT_FOUND | redemptionCode 无法匹配有效领取记录 |
| 400 | VALIDATION_ERROR | 领取记录已核销(ALREADY_REDEEMED) |
| 400 | VALIDATION_ERROR | 领取记录已过期(EXPIRED) |
| 400 | VALIDATION_ERROR | zhichong 礼券不能使用 PIN 核销 |
外部礼券履约状态
核销外部供货礼券后,请检查响应中的 externalFulfillmentStatus:
externalFulfillmentStatus | 含义 | 建议处理 |
|---|---|---|
fulfilled | 供货成功;非直充场景下通常可得到 externalVoucherCode 或 externalRedemptionUrl | 将码或链接交付用户 |
failed | 供货失败 | 可按 externalFulfillmentError 排查后,对同一领取记录重试核销 |
processing | 供货处理中(异步等少见场景) | 稍后重试,或通过 GET /users/:userId/vouchers 轮询领取状态 |
若供货方购买失败,核销请求会返回错误,领取记录仍可重试。系统会把该领取记录更新为 externalFulfillmentStatus: "failed" 并记录 externalFulfillmentError。
外部履约失败后的领取记录状态示例:
{
"_id": "507f1f77bcf86cd799439050",
"status": "active",
"externalFulfillmentStatus": "failed",
"externalFulfillmentError": "Provider returned: insufficient inventory",
"externalOrderId": null
}当 externalFulfillmentStatus 为 failed 时,领取记录仍为 active,可在修正问题后再次调用核销。
列举礼券
分页获取当前租户下可见的礼券模板列表。可选地排除当前 tenant 自管创建的礼券。
GET /vouchers查询参数:
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
page | integer | 否 | 最小 1,默认 1 | 页码(从 1 起) |
limit | integer | 否 | 1–100,默认 20 | 每页条数 |
visibility | string | 否 | 枚举:private、public、shared | 按可见性筛选 |
isActive | boolean | 否 | 字符串 "true" / "false",解析为布尔值 | 是否仅返回启用中的模板 |
category | string | 否 | 任意字符串 | 按分类标签筛选 |
search | string | 否 | 任意字符串 | 按礼券名称模糊搜索 |
subCompanyId | string | 否 | MongoDB ObjectId(24 位十六进制) | 按子公司范围筛选 |
excludeTenantManualCreated | boolean | 否 | 字符串 "true" / "false",默认 false | 为 true 时排除当前 tenant 自管创建的礼券,保留 vouchain 等外部供货礼券 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/vouchers?page=1&limit=10&isActive=true&excludeTenantManualCreated=true" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"terms": "Valid on orders over $50",
"images": ["https://example.com/image.jpg"],
"isActive": true,
"totalQuantity": 100,
"claimedQuantity": 45,
"maxClaimsPerUser": 1,
"voucherType": "discount",
"consumptionType": "coupon_code",
"externalProvider": "vouchain",
"externalId": "VCH-EXT-001",
"externalRequiresDirectOrderParams": false,
"category": "Lifestyle & Services",
"categories": ["food", "lifestyle"],
"settlementAmount": 80,
"settlementCurrency": "THB",
"startDate": "2024-01-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"visibility": "public",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 45,
"totalPages": 5,
"hasNextPage": true,
"hasPrevPage": false
}
}响应字段(data 数组每一项):
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId(24 位十六进制) | 礼券模板唯一 ID |
name | string | 否 | 至少 1 字符,已 trim | 展示名称 |
description | string | 否 | 可为空串 "" | 礼券说明 |
value | number | 否 | >= 0,默认 0 | 面额/折扣数值,具体含义见 valueType |
valueType | string | 否 | 枚举:fixed、percentage | fixed 为固定金额,percentage 为折扣比例 |
valueCurrency | string | 否 | ISO 4217(如 THB、HKD、USD),默认 THB | 当 valueType 为 fixed 时 value 的币种 |
minSpend | number | 否 | >= 0,默认 0 | 最低消费门槛;0 表示无门槛 |
maxDiscount | number | 是 | > 0;未设置可为 null | 百分比折扣的封顶金额 |
terms | string | 否 | 可为空串 "" | 使用条款 |
images | string[] | 否 | 合法 URL 数组,可为 [] | 配图 URL,首张为主图 |
isActive | boolean | 否 | true / false | 是否启用、可被领取 |
isTransferable | boolean | 否 | true / false,默认 false | 用户领取后是否允许转让 |
totalQuantity | integer | 否 | -1 表示不限量;否则 >= 0 | 可领取总量上限;当 claimedQuantity >= totalQuantity(且非 -1)时不可再领取 |
claimedQuantity | integer | 否 | >= 0 | 已被领取的次数 |
maxClaimsPerUser | integer | 否 | 0 表示每用户不限次;否则 >= 1 | 单用户最多可领取次数 |
voucherType | string | 否 | 枚举:cash、discount、product、cash_discount,默认 discount | 业务分类;勿单独用于判断外部/租户自管 |
consumptionType | string | 否 | 枚举:vio_code、coupon_code、url、qr_code、manual、zhichong | 核销交互形态,配合 externalProvider / externalId 决定对接方式 |
externalProvider | string | 是 | 供货方代码或 null | 与 externalId 同时存在时表示外部供货礼券 |
externalId | string | 是 | 供货侧模板 ID 或 null | 与 externalProvider 同时存在时表示外部供货礼券 |
externalRequiresDirectOrderParams | boolean | 是 | true / false / null | 外部核销是否需额外账户参数(如直充账号) |
externalData | object | 是 | 对象或 null | 外部供货方原始/扩展数据;结构可能因供货方而异 |
category | string | 是 | 枚举:Wellness、Health、Food & Beverage、Leisure & Entertainment、Travel & Hospitality、Lifestyle & Services、Others;可为 null | 礼券主分类,可用于分类筛选与展示 |
categories | string[] | 否 | trim 后的字符串数组,可为 [] | 旧版自定义分类标签(已弃用,建议使用 category),为兼容仍会返回 |
applicableBrands | object[] | 否 | 每项包含 _id、name、slug | 适用品牌/子公司(旧字段,建议优先使用 applicableBrandTags) |
applicableBrandTags | object[] | 否 | 每项包含 _id、name、logo | 适用品牌标签 |
settlementAmount | number | 否 | >= 0,默认 0 | 该礼券的结算单价:当此礼券在跨租户场景下被用户核销时,VIO 与发券租户之间记账的结算金额(即 VIO 给客户的结算价)。可用于客户对账 |
settlementCurrency | string | 否 | ISO 4217 代码,大写,默认 THB | settlementAmount 的币种 |
crossTenantReceivableTiming | string | 否 | 枚举:redemption、consumption,默认 redemption | 跨租户应收入账时点 |
startDate | string | 是 | ISO 8601;未设置可为 null | 生效起始时间 |
endDate | string | 是 | ISO 8601;null 表示不设结束 | 失效时间 |
visibility | string | 否 | 枚举:private、public、shared | 可见范围;亦影响用户间转让规则 |
tenantId | object | 否 | 包含 _id、name、slug | 礼券所属租户 |
subCompanyId | object | 是 | 包含 _id、name、slug;未归属子公司时可为 null | 礼券所属子公司 |
isOwn | boolean | 否 | true / false | 是否属于当前请求上下文的组织 |
ownerOrg | object | 是 | { "name": string, "type": "tenant/subCompany" };自有礼券通常不返回 | 非自有礼券的拥有方信息 |
targetTiers | string[] | 否 | 字符串数组,可为 [] | 限定可见/可领取的会员等级 |
targetUserGroups | string[] | 否 | MongoDB ObjectId 数组,可为 [] | 限定可见/可领取的用户组 |
applicableScope | string | 否 | 枚举:all_outlets、partial_outlets、single_store,默认 all_outlets | 适用门店范围 |
applicableCountry | string | 是 | ISO 国家/地区代码;可为 null | 单一适用国家/地区(旧字段) |
applicableCountries | string[] | 否 | ISO 国家/地区代码数组,可为 [] | 适用国家/地区列表 |
storeId | string | 是 | MongoDB ObjectId;可为 null | 单门店适用范围对应的门店 ID |
storeLocation | object | 是 | 包含 name、address、latitude、longitude;可为 null | 内嵌门店位置 |
bookingEnabled | boolean | 否 | true / false,默认 false | 是否启用预约 |
bookingDaysInAdvance | integer | 否 | >= 0,默认 0 | 可提前预约天数 |
consumptionMessage | string | 否 | 可为空串 "" | manual 核销方式的提示文本 |
consumptionUrl | string | 否 | 可为空串 "" | url 核销方式的链接 |
consumptionQrCode | string | 否 | 可为空串 "" | qr_code 核销方式的二维码图片路径 |
contractAddress | string | 是 | 区块链合约地址;可为 null | 礼券 NFT 合约地址 |
createdBy | string | 是 | MongoDB ObjectId;可为 null | 创建/部署该礼券的管理员用户 ID |
metadata | object | 否 | 对象,默认 {} | 扩展元数据 |
createdAt | string | 否 | ISO 8601 | 创建时间 |
updatedAt | string | 否 | ISO 8601 | 最近更新时间 |
创建礼券
为当前租户新建一条礼券模板。
POST /vouchers请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
name | string | 是 | 至少 1 字符,已 trim | 会员端展示的礼券名称 |
description | string | 否 | 无最大长度,默认 "" | 礼券说明,纯文本 |
voucherType | string | 否 | 枚举:cash、discount、product、cash_discount,默认 discount | 礼券业务类型 |
visibility | string | 否 | 枚举:private、public、shared,默认 private | 可见范围:private 本租户;public 全平台;shared 仅 sharedWithTenants 中租户 |
sharedWithTenants | string[] | 否 | 每项为 MongoDB ObjectId(24 位十六进制) | 共享目标租户 ID 列表;仅当 visibility 为 shared 时有效 |
sharedWithUsers | string[] | 否 | 每项为 MongoDB ObjectId | 限定可见的用户 ID,用于定向投放 |
terms | string | 否 | 无最大长度,默认 "" | 领取/核销前向用户展示的条款 |
value | number | 否 | 最小 0,默认 0 | 面额或折扣数值:valueType 为 fixed 时为扣减金额;为 percentage 时为百分比(如 20 表示 20% off) |
valueType | string | 否 | 枚举:fixed、percentage,默认 fixed | value 的语义:固定金额或比例 |
valueCurrency | string | 否 | ISO 4217(如 THB、HKD、USD),默认 THB | 当 valueType 为 fixed 时 value 的币种 |
minSpend | number | 否 | 最小 0,默认 0 | 最低消费门槛;0 表示无门槛 |
maxDiscount | number | 否 | 须 > 0(若填写) | 折扣上限,常见于百分比券(如八折但最多减 100) |
totalQuantity | integer | 否 | 整数,-1 表示不限量,默认 -1 | 可领取总次数上限;抢完后不可再领取 |
startDate | string | 否 | ISO 8601,默认当前时间 | 开始可领取时间;省略表示立即生效 |
endDate | string | 否 | ISO 8601,须晚于 startDate | 过期时间;省略表示不设过期;过期后不可领取/核销 |
maxClaimsPerUser | integer | 否 | 整数,最小 0,0 表示每用户不限次,默认 1 | 单用户最多可领取次数 |
settlementAmount | number | 否 | 最小 0,默认 0 | 跨租户核销时的法币结算金额 |
settlementCurrency | string | 否 | ISO 4217,存盘为大写,默认 THB | settlementAmount 币种;默认取租户账单设置主币种;结算记录均以此币种记账 |
category | string | 否 | 枚举:Wellness、Health、Food & Beverage、Leisure & Entertainment、Travel & Hospitality、Lifestyle & Services、Others | 礼券主分类,建议优先使用;例如 "Others" |
categories | string[] | 否 | 字符串数组,元素已 trim | 分类标签,如 ["food", "lifestyle"] |
images | string[] | 否 | 每项为合法 URL | 配图,首图为主图 |
targetTiers | string[] | 否 | 字符串数组,元素已 trim | 会员等级名称;仅这些等级可见/可领 |
targetUserGroups | string[] | 否 | 每项为 MongoDB ObjectId | 用户组 ID;仅组内用户可见/可领 |
subCompanyId | string | 否 | MongoDB ObjectId | 将礼券归属到指定子公司 |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale 20% Off",
"description": "Valid for summer collection",
"value": 20,
"valueType": "percentage",
"category": "Others",
"totalQuantity": 100,
"maxClaimsPerUser": 1,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"visibility": "public",
"terms": "Cannot be combined with other offers"
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439012",
"name": "Summer Sale 20% Off",
"description": "Valid for summer collection",
"value": 20,
"valueType": "percentage",
"totalQuantity": 100,
"claimedQuantity": 0,
"maxClaimsPerUser": 1,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"visibility": "public",
"isActive": true,
"createdAt": "2024-05-15T10:00:00.000Z"
},
"message": "Voucher created"
}响应字段:
返回新建后的完整礼券对象。大多数字段与「列举礼券」响应字段一致,但 isOwn、ownerOrg 等列表接口计算字段不会由创建接口额外添加。
获取礼券详情
获取指定礼券模板的完整信息。
GET /vouchers/:voucherId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
voucherId | string | 是 | 礼券模板 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"terms": "Valid on orders over $50",
"images": ["https://example.com/image.jpg"],
"isActive": true,
"totalQuantity": 100,
"claimedQuantity": 45,
"maxClaimsPerUser": 1,
"startDate": "2024-01-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"visibility": "public",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}响应字段:
返回完整礼券对象。大多数字段与「列举礼券」响应字段一致,但 isOwn、ownerOrg 等列表接口计算字段不会由详情接口额外添加。若礼券为 shared,sharedWithTenants、sharedWithSubCompanies、sharedWithUsers 可能返回已填充的摘要对象。
更新礼券
更新已有礼券模板(部分字段)。
PATCH /vouchers/:voucherId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
voucherId | string | 是 | 礼券模板 ID |
请求体:
所有字段均为可选,仅传需要修改的字段。
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
name | string | 至少 1 字符 | 展示名称 |
description | string | 无最大长度 | 说明 |
voucherType | string | 枚举:cash、discount、product、cash_discount | 礼券业务类型 |
visibility | string | 枚举:private、public、shared | 可见范围 |
sharedWithTenants | string[] | 每项为 MongoDB ObjectId | 共享租户 ID(仅 visibility 为 shared 时) |
sharedWithUsers | string[] | 每项为 MongoDB ObjectId | 定向投放的用户 ID |
terms | string | 无最大长度 | 条款 |
value | number | 最小 0 | 面额/折扣数值 |
valueType | string | 枚举:fixed、percentage | value 语义 |
valueCurrency | string | ISO 4217 | fixed 时的币种 |
minSpend | number | 最小 0 | 最低消费,0 表示无门槛 |
maxDiscount | number | 须 > 0(若填写) | 折扣封顶(常见于百分比券) |
totalQuantity | integer | 整数,-1 表示不限量 | 可领取总量 |
startDate | string | ISO 8601 | 生效时间 |
endDate | string | ISO 8601 | 失效时间 |
maxClaimsPerUser | integer | 最小 0,0 表示不限 | 单用户领取上限 |
settlementAmount | number | 最小 0 | 跨租户结算金额 |
settlementCurrency | string | ISO 4217,存盘大写 | 结算币种;默认租户账单主币种 |
category | string | 枚举:Wellness、Health、Food & Beverage、Leisure & Entertainment、Travel & Hospitality、Lifestyle & Services、Others | 礼券主分类,例如 "Others" |
isActive | boolean | true / false | 是否启用 |
categories | string[] | 每项已 trim | 分类标签 |
images | string[] | 每项为合法 URL | 配图 |
targetTiers | string[] | 每项已 trim | 目标会员等级 |
targetUserGroups | string[] | 每项为 MongoDB ObjectId | 目标用户组 |
请求示例:
curl -X PATCH "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Voucher Name",
"isActive": false
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439011",
"name": "Updated Voucher Name",
"isActive": false,
"updatedAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher updated"
}删除礼券
软删除礼券模板(标记删除,非物理清除)。
DELETE /vouchers/:voucherId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
voucherId | string | 是 | 礼券模板 ID |
请求示例:
curl -X DELETE "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439011",
"isDeleted": true,
"isActive": false,
"deletedAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher deleted"
}响应字段:
返回软删除后的礼券对象。关键字段为 _id、isDeleted: true、isActive: false、deletedAt;响应中也可能包含其他礼券字段。
复制礼券
基于现有模板复制一条新礼券。
POST /vouchers/:voucherId/duplicate路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
voucherId | string | 是 | 待复制的礼券模板 ID |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011/duplicate" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439013",
"name": "20% Off Discount (Copy)",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"claimedQuantity": 0,
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher duplicated"
}响应字段:
返回复制后的完整礼券对象。副本会复制原模板字段,将 name 加上 (Copy) 后缀,将 claimedQuantity 重置为 0,并部署新的 contractAddress。
向用户发放礼券(send)
将礼券直接发放给指定用户:系统会铸造对应 NFT(如链上流程尚未完成则可能延后),并创建一条状态为 active 的用户礼券记录;不经过活动领取流程。
POST /vouchers/:voucherId/send路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
voucherId | string | 是 | 礼券模板 ID |
请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
userId | string | 是 | 至少 1 字符,MongoDB ObjectId(24 位十六进制) | 接收用户 ID |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/507f1f77bcf86cd799439011/send" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015"
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439050",
"userId": "507f1f77bcf86cd799439015",
"voucherId": "507f1f77bcf86cd799439011",
"tenantId": "507f1f77bcf86cd799439001",
"nftTokenId": "42",
"status": "active",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"expiresAt": "2024-08-31T23:59:59.000Z",
"settlementAmount": 0,
"settlementCurrency": "HKD",
"claimedAt": "2024-02-01T12:00:00.000Z",
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Voucher sent to user"
}响应字段:
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId | 用户礼券记录(UserVoucher)ID |
userId | string | 否 | MongoDB ObjectId | 接收用户 |
voucherId | string | 否 | MongoDB ObjectId | 礼券模板 ID |
tenantId | string | 否 | MongoDB ObjectId | 所属租户 |
nftTokenId | string | 是 | 链上 token ID 的数字字符串;铸造中可为 null | NFT token ID |
status | string | 否 | 枚举:active、claimed、redeemed、expired;新发放恒为 active | 领取记录状态 |
redemptionCode | string | 否 | 格式 VCH-{base36 时间戳}-{4 位随机} | 用户出示用于核销的码 |
expiresAt | string | 是 | ISO 8601;null 表示不设过期 | 该条领取的过期时间,通常继承模板 endDate |
settlementAmount | number | 否 | >= 0,默认 0 | 发放时自模板复制的跨租户结算金额 |
settlementCurrency | string | 否 | ISO 4217 大写 | settlementAmount 币种 |
claimedAt | string | 否 | ISO 8601 | 发放(到账)时间 |
createdAt | string | 否 | ISO 8601 | 记录创建时间 |
错误响应:
| HTTP 状态 | 错误码 | 说明 |
|---|---|---|
| 400 | VALIDATION_ERROR | 礼券未启用、已过期、已抢完,或用户已达领取上限 |
| 404 | NOT_FOUND | 礼券或用户不存在 |
列举礼券领取记录
分页查询当前租户下所有用户礼券(领取)记录。
GET /vouchers/claims/list查询参数:
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
page | string | 否 | 解析为整数,最小 1,默认 1 | 页码 |
limit | string | 否 | 解析为整数,最小 1,默认 20 | 每页条数 |
voucherId | string | 否 | MongoDB ObjectId | 按礼券模板 ID 筛选 |
status | string | 否 | 枚举:active、claimed、redeemed、expired | 按领取记录状态筛选 |
fromDate | string | 否 | ISO 8601 | 起始时间(含) |
toDate | string | 否 | ISO 8601 | 结束时间(含) |
search | string | 否 | 任意字符串 | 按用户姓名或邮箱模糊搜索 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/vouchers/claims/list?status=active&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439014",
"voucherId": {
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"images": ["https://example.com/image.jpg"],
"value": 20,
"valueType": "percentage"
},
"userId": {
"_id": "507f1f77bcf86cd799439015",
"displayName": "John Doe",
"email": "john@example.com",
"avatar": "https://example.com/avatar.jpg"
},
"tenantId": "507f1f77bcf86cd799439001",
"status": "active",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"claimedAt": "2024-01-15T14:30:00.000Z",
"expiresAt": "2024-12-31T23:59:59.000Z",
"settlementAmount": 80,
"settlementCurrency": "THB"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 45,
"totalPages": 5,
"hasNextPage": true,
"hasPrevPage": false
}
}响应字段(data 数组每一项):
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId | 领取记录 ID |
voucherId | object | 否 | 已填充的礼券摘要对象 | 关联礼券模板 |
voucherId._id | string | 否 | MongoDB ObjectId | 礼券模板 ID |
voucherId.name | string | 否 | 已 trim | 礼券名称 |
voucherId.images | string[] | 否 | URL 数组,可为 [] | 礼券图片 |
voucherId.value | number | 否 | >= 0 | 礼券数值 |
voucherId.valueType | string | 否 | 枚举:fixed、percentage | 数值语义 |
userId | object | 否 | 已填充的用户摘要对象 | 接收/领取该礼券的用户 |
userId._id | string | 否 | MongoDB ObjectId | 用户 ID |
userId.displayName | string | 是 | 可能为 null | 用户展示名 |
userId.email | string | 是 | 邮箱 | 用户邮箱 |
userId.avatar | string | 是 | URL 或 null | 用户头像 |
tenantId | string | 否 | MongoDB ObjectId | 领取记录所属租户 |
voucherOwnerTenantId | string | 是 | MongoDB ObjectId | 礼券模板所属租户 |
nftTokenId | string | 是 | 链上 token ID,可为 null | 该领取记录对应的 NFT token |
status | string | 否 | 枚举:active、claimed、redeemed、expired | 当前状态 |
redemptionCode | string | 否 | 格式 VCH-{base36 时间戳}-{4 位随机} | 核销码 |
claimedAt | string | 否 | ISO 8601 | 领取/发放时间 |
redeemedAt | string | 是 | ISO 8601,可为 null | 核销时间 |
expiresAt | string | 是 | ISO 8601,null 表示不设过期 | 该条记录过期时间 |
campaignId | string | 是 | MongoDB ObjectId,可为 null | 关联活动 ID |
settlementAmount | number | 否 | >= 0,默认 0 | 领取时记录的结算金额 |
settlementCurrency | string | 否 | ISO 4217 | 结算币种 |
tokenAmountPaid | string | 否 | 数字字符串,默认 "0" | 领取时支付的代币数量 |
redemptionDetails | object | 是 | 对象或 null | 核销后的元数据 |
externalVoucherCode | string | 是 | 字符串或 null | 外部供货返回的券码 |
externalRedemptionUrl | string | 是 | URL 或 null | 外部供货返回的核销链接 |
externalOrderId | string | 是 | 供货方订单 ID 或 null | 外部供货订单 ID |
externalBuyStatus | string | 是 | 枚举:pending、recorded、failed、null | 外部购买记录状态 |
externalFulfillmentStatus | string | 是 | 枚举:pending、processing、fulfilled、failed、null | 外部履约状态 |
externalFulfillmentError | string | 是 | 字符串或 null | 外部履约错误详情 |
externalIsDirectRecharge | boolean | 否 | true / false,默认 false | 是否为直充履约 |
externalRechargeAccount | string | 是 | 字符串或 null | 直充账号 |
metadata | object | 否 | 对象,默认 {} | 扩展元数据 |
createdAt | string | 否 | ISO 8601 | 记录创建时间 |
updatedAt | string | 否 | ISO 8601 | 最近更新时间 |
按核销码核销
根据用户出示的核销码完成核销;适用于门店收银、自助结算等场景(API Key 鉴权)。
POST /vouchers/redeem-by-code请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
redemptionCode | string | 是 | 至少 1 字符,区分大小写 | 领取记录上的核销码(如 VCH-M1ABC2-XY3Z) |
location | string | 否 | 无最大长度 | 核销地点(如门店名、分店) |
notes | string | 否 | 无最大长度 | 备注(如订单号) |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/redeem-by-code" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"redemptionCode": "ABC123XYZ",
"location": "Store #42",
"notes": "Customer purchased item XYZ"
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439014",
"voucherId": "507f1f77bcf86cd799439011",
"userId": "507f1f77bcf86cd799439015",
"status": "redeemed",
"redemptionCode": "VCH-M1ABC2-XY3Z",
"claimedAt": "2024-01-15T14:30:00.000Z",
"redeemedAt": "2024-02-01T10:15:00.000Z",
"redemptionDetails": {
"location": "Store #42",
"notes": "Customer purchased item XYZ",
"method": "api_key",
"redeemedBy": "api:507f1f77bcf86cd799439099",
"redeemedByType": "api_key",
"redeemedByApiKey": "507f1f77bcf86cd799439099"
},
"externalVoucherCode": "ABC-123-XYZ",
"externalRedemptionUrl": null,
"externalOrderId": "69f75fe31134f32e472b2f98",
"externalFulfillmentStatus": "fulfilled"
},
"message": "Voucher redeemed"
}响应字段:
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId | 领取记录 ID |
voucherId | string 或 object | 否 | MongoDB ObjectId 或已填充的礼券对象 | 礼券模板 |
userId | string | 否 | MongoDB ObjectId | 礼券持有人原用户 ID |
status | string | 否 | 成功后为 redeemed | 状态 |
redemptionCode | string | 否 | 格式 VCH-{base36 时间戳}-{4 位随机} | 已核销的码 |
claimedAt | string | 否 | ISO 8601 | 原领取时间 |
redeemedAt | string | 否 | ISO 8601 | 核销时间 |
redemptionDetails.location | string | 是 | 请求未传则可能不存在 | 核销地点 |
redemptionDetails.notes | string | 是 | 请求未传则可能不存在 | 核销备注 |
redemptionDetails.method | string | 否 | api_key | 核销方式 |
redemptionDetails.redeemedBy | string | 否 | api:{apiKeyId} | 记录为核销主体的 API Key |
redemptionDetails.redeemedByType | string | 否 | api_key | 核销主体类型 |
redemptionDetails.redeemedByApiKey | string | 是 | MongoDB ObjectId | API Key ID |
externalVoucherCode | string | 是 | 字符串或 null | 外部供货返回的券码 |
externalRedemptionUrl | string | 是 | URL 或 null | 外部供货返回的核销链接 |
externalOrderId | string | 是 | 供货方订单 ID 或 null | 外部供货订单 ID |
externalFulfillmentStatus | string | 是 | 枚举:fulfilled、failed、processing、pending、null | 外部履约状态 |
按核销码查询信息(核销前)
在正式核销前根据核销码拉取礼券与领取信息,用于收银台展示与校验。
GET /vouchers/redeem/:code/info路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 核销码 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/vouchers/redeem/ABC123XYZ/info" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"redemptionCode": "VCH-M1ABC2-XY3Z",
"status": "active",
"voucher": {
"name": "20% Off Discount",
"description": "Get 20% off your next purchase",
"value": 20,
"valueType": "percentage",
"valueCurrency": "THB",
"image": "https://example.com/image.jpg",
"terms": "Valid on orders over $50"
},
"tenant": {
"name": "Demo Tenant",
"logo": "https://example.com/logo.png",
"slug": "demo-tenant"
},
"pinPrefix": "VI",
"expiresAt": "2024-12-31T23:59:59.000Z",
"canRedeem": true
}
}响应字段:
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
redemptionCode | string | 否 | 核销码 | 本次查询的核销码 |
status | string | 否 | 枚举:active、claimed、redeemed、expired | 展示状态;已过期的 active 记录返回 expired |
voucher | object | 是 | 对象或 null | 礼券展示摘要 |
voucher.name | string | 否 | 已 trim | 礼券名称 |
voucher.description | string | 否 | 可为 "" | 说明 |
voucher.value | number | 否 | >= 0 | 面额/折扣数值 |
voucher.valueType | string | 否 | 枚举:fixed、percentage | 数值语义 |
voucher.valueCurrency | string | 否 | ISO 4217 | fixed 类时的币种 |
voucher.image | string | 是 | URL 或 null | 第一张礼券图片 |
voucher.terms | string | 否 | 可为 "" | 条款 |
tenant | object | 是 | 对象或 null | 礼券创建方租户摘要 |
tenant.name | string | 否 | 文本 | 租户名称 |
tenant.logo | string | 是 | URL 或 null | 租户 logo |
tenant.slug | string | 是 | slug 或 null | 租户 slug |
pinPrefix | string | 否 | 文本,如 VI | PIN 核销时应输入的前缀 |
expiresAt | string | 是 | ISO 8601,null 表示不设过期 | 该条领取记录的过期时间 |
canRedeem | boolean | 否 | true / false | 当前状态是否允许核销 |
店员 PIN 核销(consume)
使用店员个人 PIN 在核销前完成身份校验,再将礼券标记为已消费。适用于门店需落实到具体店员责任的场景。
重要:PIN 校验归属「创建礼券」的组织。 PIN 与创建该礼券模板的租户(或子公司)绑定,而非分发活动的组织。跨租户场景下(例如 A 公司创建礼券、B 公司通过活动分发),会员须在 A 公司门店 由 A 公司店员输入 PIN。若礼券由子公司创建,仅该公司子公司的 PIN 有效。
PIN 权限: 店员 PIN 须具备
voucher_redemption权限方可核销礼券;仅有token_claim的 PIN 不能用于礼券核销。PIN 权限配置见管理后台文档。
按码核销(redeem-by-code)与本接口(PIN)的区别
- 按码核销(
POST /vouchers/redeem-by-code):凭 API Key 鉴权,核销主体记为 API Key 对应方;适合服务端对接。- PIN 核销(
POST /vouchers/redeem/:code/pin):凭店员 PIN 鉴权,核销主体记为具体店员;适合门店落实到人。
POST /vouchers/redeem/:code/pin路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 领取记录上的核销码 |
请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
pin | string | 是 | 5–20 字符;常见为 2 位字母前缀 + 4 位数字(如 HA1234);须具备 voucher_redemption 权限 | 店员 PIN |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/vouchers/redeem/ABC123XYZ/pin" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"pin": "HA1234"
}'响应示例:
{
"success": true,
"data": {
"success": true,
"voucher": {
"name": "20% Off Discount",
"value": 20,
"valueType": "percentage",
"valueCurrency": "THB"
},
"redeemedAt": "2024-02-01T10:15:00.000Z",
"redeemedBy": "John Staff"
},
"message": "Voucher consumed"
}响应字段:
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
success | boolean | 否 | 成功时为 true | 业务成功标记 |
voucher.name | string | 否 | 已 trim | 已核销礼券名称 |
voucher.value | number | 否 | >= 0 | 面额/折扣数值 |
voucher.valueType | string | 否 | 枚举:fixed、percentage | 数值语义 |
voucher.valueCurrency | string | 否 | ISO 4217 | fixed 类时的币种 |
redeemedAt | string | 否 | ISO 8601 | 核销时间 |
redeemedBy | string | 否 | 文本 | 校验 PIN 的店员展示名 |
错误响应:
| HTTP 状态 | 错误码 | 说明 |
|---|---|---|
| 400 | VALIDATION_ERROR | PIN 格式无效(须 5–20 字符) |
| 400 | VALIDATION_ERROR | PIN 无效(前缀或号码与任一店员不匹配) |
| 400 | VALIDATION_ERROR | PIN 缺少 voucher_redemption 权限 |
| 400 | VALIDATION_ERROR | 礼券已核销或已过期 |
| 404 | NOT_FOUND | 无此核销码对应的礼券记录 |
7. API 参考:用户(Users)
所需 Scope: users
列举用户
分页获取当前租户下的用户列表。
GET /users查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | integer | 否 | 页码(默认 1) |
limit | integer | 否 | 每页条数(默认 20) |
role | string | 否 | 按角色筛选:super_admin、tenant_admin、sub_company_admin、member |
search | string | 否 | 按邮箱、手机号或姓名搜索 |
isActive | boolean | 否 | 是否仅返回启用用户 |
subCompanyId | string | 否 | 按子公司 ID 筛选 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/users?role=member&isActive=true&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439015",
"email": "john@example.com",
"phone": "+66812345678",
"displayName": "John Doe",
"role": "member",
"isActive": true,
"walletAddress": "0x1234567890abcdef...",
"registrationSource": "direct",
"storeId": null,
"campaignId": null,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 150,
"totalPages": 15,
"hasNextPage": true,
"hasPrevPage": false
}
}创建用户
在当前租户下创建新用户。
POST /users请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 否* | 邮箱地址 |
phone | string | 否* | 手机号 |
password | string | 是 | 密码(至少 6 位) |
displayName | string | 否 | 展示名 |
role | string | 否 | member 或 sub_company_admin(默认 member) |
subCompanyId | string | 否 | 指派到的子公司 ID |
metadata | object | 否 | 自定义键值元数据 |
*
phone至少填一项。
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/users" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"email": "newuser@example.com",
"phone": "+66812345678",
"password": "securepassword123",
"displayName": "New User",
"role": "member",
"metadata": {
"referralSource": "website",
"tier": "gold"
}
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439016",
"email": "newuser@example.com",
"phone": "+66812345678",
"displayName": "New User",
"role": "member",
"isActive": true,
"walletAddress": "0xabcdef1234567890...",
"metadata": {
"referralSource": "website",
"tier": "gold"
},
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "User created"
}获取用户详情
按用户 ID 获取详细信息。
GET /users/:userId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439015",
"email": "john@example.com",
"phone": "+66812345678",
"displayName": "John Doe",
"role": "member",
"isActive": true,
"walletAddress": "0x1234567890abcdef...",
"avatar": "https://example.com/avatar.jpg",
"metadata": {
"tier": "gold"
},
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}更新用户
更新已有用户信息。
PATCH /users/:userId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
请求体:
| 字段 | 类型 | 说明 |
|---|---|---|
displayName | string | 展示名 |
avatar | string | 头像 URL |
isActive | boolean | 是否启用 |
subCompanyId | string | 子公司 ID |
metadata | object | 自定义元数据 |
请求示例:
curl -X PATCH "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"displayName": "John Smith",
"metadata": {
"tier": "platinum"
}
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439015",
"displayName": "John Smith",
"metadata": {
"tier": "platinum"
},
"updatedAt": "2024-02-01T12:00:00.000Z"
},
"message": "User updated"
}停用用户
软删除用户(将 isActive 置为 false)。
DELETE /users/:userId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
请求示例:
curl -X DELETE "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": null,
"message": "User deactivated"
}获取用户代币余额
查询指定用户在各代币下的可用余额(含锁定余额)。
GET /users/:userId/balances路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015/balances" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"tokenId": "507f1f77bcf86cd799439020",
"tokenName": "Loyalty Points",
"symbol": "LP",
"balance": "1500",
"lockedBalance": "0"
},
{
"tokenId": "507f1f77bcf86cd799439021",
"tokenName": "Reward Coins",
"symbol": "RC",
"balance": "250",
"lockedBalance": "50"
}
]
}获取用户已领取礼券
分页返回该用户已领取的礼券记录。
GET /users/:userId/vouchers路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | integer | 否 | 页码(默认 1) |
limit | integer | 否 | 每页条数(默认 20) |
status | string | 否 | 按状态筛选:active、redeemed、expired |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/users/507f1f77bcf86cd799439015/vouchers?status=active" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439014",
"voucherId": "507f1f77bcf86cd799439011",
"status": "active",
"redemptionCode": "ABC123XYZ",
"claimedAt": "2024-01-15T14:30:00.000Z",
"expiresAt": "2024-12-31T23:59:59.000Z",
"voucher": {
"name": "20% Off Discount",
"value": 20,
"valueType": "percentage"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 5,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}按标识符搜索用户
通过邮箱、手机号或钱包地址定位用户。
GET /users/search/by-identifier查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 否* | 邮箱 |
phone | string | 否* | 手机号 |
walletAddress | string | 否* | 钱包地址 |
*至少提供上述之一。
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/users/search/by-identifier?email=john@example.com" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439015",
"email": "john@example.com",
"phone": "+66812345678",
"displayName": "John Doe",
"role": "member",
"isActive": true,
"walletAddress": "0x1234567890abcdef..."
}
}8. API 参考:活动(Campaigns)
所需 Scope: campaigns
列举活动
分页获取当前租户下的礼券活动列表。
GET /campaigns查询参数:
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
page | string | 否 | 解析为整数,最小 1,默认 1 | 页码(从 1 起) |
limit | string | 否 | 解析为整数,最小 1,默认 20 | 每页条数 |
search | string | 否 | 任意字符串 | 按活动名称模糊搜索 |
isActive | string | 否 | 字符串 "true" / "false",解析为布尔值 | 按是否启用筛选 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/campaigns?isActive=true&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439030",
"name": "Summer Promotion",
"description": "Summer 2024 voucher campaign",
"slug": "summer-promotion",
"tokenId": "507f1f77bcf86cd799439020",
"isActive": true,
"isPublic": true,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"images": ["https://example.com/campaign.jpg"],
"createdAt": "2024-05-15T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 5,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}响应字段(data 数组每一项):
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId(24 位十六进制) | 活动唯一 ID |
name | string | 否 | 至少 1 字符,已 trim | 活动展示名称 |
description | string | 否 | 可为空串 "" | 活动说明 |
slug | string | 否 | 小写、URL 安全字符;租户+子公司内唯一 | 活动落地页友好路径(如 summer-promotion) |
tokenId | string | 否 | MongoDB ObjectId(24 位十六进制) | 用户领取礼券时需消耗的代币 ID |
isActive | boolean | 否 | true / false | 活动是否启用 |
isPublic | boolean | 否 | true / false | 是否公开(免登录可访问) |
startDate | string | 是 | ISO 8601;未传时默认为创建时间 | 活动开始时间 |
endDate | string | 是 | ISO 8601;null 表示不设结束时间 | 活动结束时间 |
images | string[] | 否 | 合法 URL 数组,可为 [] | 横幅/封面图 |
createdAt | string | 否 | ISO 8601 | 创建时间 |
创建活动
新建一条礼券活动。
POST /campaigns请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
name | string | 是 | 至少 1 字符,已 trim | 活动名称;未传 slug 时据此自动生成 |
description | string | 否 | 无最大长度,默认 "" | 活动说明 |
slug | string | 否 | 小写、URL 安全;租户+子公司内唯一;省略时对 name 做 slugify | 活动页路径标识(如 summer-promotion-2024) |
tokenId | string | 是 | MongoDB ObjectId,须为已存在的 Token | 用户领取消耗用的代币 |
startDate | string | 否 | ISO 8601,默认当前时间 | 开始接受领取的时间 |
endDate | string | 否 | ISO 8601,宜晚于 startDate;省略表示不设结束 | 活动结束时间 |
isActive | boolean | 否 | 默认 true | 停用后对用户隐藏 |
isPublic | boolean | 否 | 默认 true | 是否在公开页展示(无需登录) |
images | string[] | 否 | 每项为合法 URL,可为 [];首张为主图 | 横幅/配图 URL |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/campaigns" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Promotion 2024",
"description": "Exclusive summer deals for loyal customers",
"tokenId": "507f1f77bcf86cd799439020",
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"isPublic": true
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439031",
"name": "Summer Promotion 2024",
"description": "Exclusive summer deals for loyal customers",
"slug": "summer-promotion-2024",
"tokenId": "507f1f77bcf86cd799439020",
"isActive": true,
"isPublic": true,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"createdAt": "2024-05-15T10:00:00.000Z"
},
"message": "Campaign created"
}响应字段:
返回新建的活动对象;各字段含义参见 列举活动 中的响应字段。
获取活动详情
按活动 ID 获取单个活动的完整信息。
GET /campaigns/:campaignId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439030",
"name": "Summer Promotion",
"description": "Summer 2024 voucher campaign",
"slug": "summer-promotion",
"tokenId": "507f1f77bcf86cd799439020",
"isActive": true,
"isPublic": true,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-08-31T23:59:59.000Z",
"images": ["https://example.com/campaign.jpg"],
"createdAt": "2024-05-15T10:00:00.000Z"
}
}响应字段:
返回完整活动对象;各字段含义参见 列举活动 中的响应字段。
更新活动
更新已有活动信息。
PATCH /campaigns/:campaignId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
请求体:
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
name | string | 至少 1 字符,已 trim | 活动展示名称 |
description | string | 无最大长度 | 活动说明 |
tokenId | string | MongoDB ObjectId,须引用已存在代币 | 活动消耗的代币 |
startDate | string | ISO 8601 | 开始时间 |
endDate | string | ISO 8601 | 结束时间 |
isActive | boolean | true / false | 是否启用 |
isPublic | boolean | true / false | 是否公开页可见 |
images | string[] | 每项为合法 URL | 配图 URL |
请求示例:
curl -X PATCH "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Extended Summer Promotion",
"endDate": "2024-09-30T23:59:59.000Z"
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439030",
"name": "Extended Summer Promotion",
"endDate": "2024-09-30T23:59:59.000Z",
"updatedAt": "2024-08-15T10:00:00.000Z"
},
"message": "Campaign updated"
}删除活动
删除指定活动。
DELETE /campaigns/:campaignId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
请求示例:
curl -X DELETE "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439030"
},
"message": "Campaign deleted"
}响应字段:
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId(24 位十六进制) | 已删活动 ID |
获取活动关联礼券
分页返回已加入该活动的礼券模板及活动内配置。
GET /campaigns/:campaignId/vouchers路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | integer | 否 | 页码(默认 1) |
limit | integer | 否 | 每页条数(默认 20) |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"value": 20,
"valueType": "percentage",
"tokenAmount": "100",
"sortOrder": 1,
"isActive": true
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 3,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}响应字段(data 数组每一项):
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId | 礼券模板 ID |
name | string | 否 | 已 trim | 礼券名称 |
value | number | 否 | >= 0 | 面额/折扣数值,含义见 valueType |
valueType | string | 否 | 枚举:fixed、percentage | 数值语义 |
tokenAmount | string | 否 | 数字字符串;"0" 表示活动级不额外限制库存 | 活动内该礼券的代币定价/活动库存上限 |
sortOrder | integer | 否 | 默认 0,越小越靠前 | 在活动内的展示顺序 |
isActive | boolean | 否 | true / false | 该礼券在本活动中是否可领 |
向活动添加礼券
将礼券模板加入活动,并设置活动维度代币定价/库存上限。
POST /campaigns/:campaignId/vouchers路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
voucherIds | string[] | 是 | 至少 1 个 ObjectId;同一活动内重复提交会被拒绝 | 要加入活动的礼券模板 ID 列表 |
tokenAmount | string | 是 | 非空字符串,高精度存储;"0" 表示不在活动层做额外库存上限 | 这些礼券在活动内的代币/库存上限配置 |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"voucherIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"],
"tokenAmount": "100"
}'响应示例:
{
"success": true,
"data": {
"added": 2
},
"message": "Vouchers added to campaign"
}响应字段:
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
added | integer | >= 0 | 成功加入活动的礼券条数 |
更新活动内礼券配置
调整某礼券在该活动内的排序、库存上限及是否可领取等。
PATCH /campaigns/:campaignId/vouchers/:voucherId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
voucherId | string | 是 | 礼券 ID |
请求体:
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
tokenAmount | string | 数字字符串;"0" 表示活动层不额外限制 | 本礼券在该活动内的库存/代币上限 |
sortOrder | integer | 可为负数,默认 0,越小越靠前 | 活动内展示顺序 |
isActive | boolean | true / false | 是否允许在本活动中领取(可临时关闭) |
请求示例:
curl -X PATCH "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"tokenAmount": "150",
"sortOrder": 1
}'响应示例:
{
"success": true,
"data": {
"campaignId": "507f1f77bcf86cd799439030",
"voucherId": "507f1f77bcf86cd799439011",
"tokenAmount": "150",
"sortOrder": 1
},
"message": "Campaign voucher updated"
}响应字段:
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
campaignId | string | MongoDB ObjectId | 活动 ID |
voucherId | string | MongoDB ObjectId | 礼券 ID |
tokenAmount | string | 数字字符串 | 更新后的活动库存上限 |
sortOrder | integer | 越小越靠前 | 更新后的排序 |
从活动移除礼券
将礼券从活动中移除(不删除礼券模板本身)。
DELETE /campaigns/:campaignId/vouchers/:voucherId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
voucherId | string | 是 | 礼券 ID |
请求示例:
curl -X DELETE "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/vouchers/507f1f77bcf86cd799439011" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": null,
"message": "Voucher removed from campaign"
}可取加入活动的礼券列表
返回尚未加入该活动、可被添加的礼券模板(分页)。
GET /campaigns/:campaignId/available-vouchers路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaignId | string | 是 | 活动 ID |
查询参数:
| 参数 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
page | string | 否 | 解析为整数,最小 1,默认 1 | 页码 |
limit | string | 否 | 解析为整数,最小 1,默认 20 | 每页条数 |
search | string | 否 | 任意字符串 | 按礼券名称模糊搜索 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/campaigns/507f1f77bcf86cd799439030/available-vouchers?search=discount" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439013",
"name": "10% New User Discount",
"value": 10,
"valueType": "percentage",
"isActive": true
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 10,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}响应字段(data 数组每一项):
| 字段 | 类型 | 可空 | 约束 / 格式 | 说明 |
|---|---|---|---|---|
_id | string | 否 | MongoDB ObjectId | 礼券模板 ID |
name | string | 否 | 已 trim | 名称 |
value | number | 否 | >= 0 | 面额/折扣 |
valueType | string | 否 | fixed / percentage | 数值语义 |
isActive | boolean | 否 | true / false | 模板是否启用 |
9. API 参考:代币(Tokens)
所需 Scope: tokens
列举代币
分页列出当前租户下的代币定义。
GET /tokens查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | integer | 否 | 页码(默认 1) |
limit | integer | 否 | 每页条数(默认 20) |
isActive | boolean | 否 | 按是否启用筛选 |
subCompanyId | string | 否 | 按子公司筛选 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/tokens?isActive=true" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439020",
"name": "Loyalty Points",
"symbol": "LP",
"description": "Earn points on every purchase",
"totalSupply": "1000000",
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 2,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
}
}获取代币详情
按代币 ID 获取代币元数据。
GET /tokens/:tokenId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439020",
"name": "Loyalty Points",
"symbol": "LP",
"description": "Earn points on every purchase",
"totalSupply": "1000000",
"isActive": true,
"decimals": 0,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}获取代币统计
返回某代币的供应量、持有人数等汇总指标。
GET /tokens/:tokenId/stats路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/stats" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"totalSupply": "1000000",
"circulatingSupply": "750000",
"holdersCount": 1250,
"totalTransactions": 15000,
"averageBalance": "600"
}
}获取代币持有者
分页列出持有该代币的用户及其余额。
GET /tokens/:tokenId/holders路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | integer | 否 | 页码(默认 1) |
limit | integer | 否 | 每页条数(默认 20) |
sort | string | 否 | 排序:balance、name、recent(默认 balance) |
search | string | 否 | 按用户姓名或邮箱搜索 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/holders?sort=balance&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"user": {
"_id": "507f1f77bcf86cd799439015",
"displayName": "John Doe",
"email": "john@example.com"
},
"balance": "5000"
},
{
"user": {
"_id": "507f1f77bcf86cd799439016",
"displayName": "Jane Smith",
"email": "jane@example.com"
},
"balance": "3500"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 1250,
"totalPages": 125,
"hasNextPage": true,
"hasPrevPage": false
},
"token": {
"_id": "507f1f77bcf86cd799439020",
"name": "Loyalty Points",
"symbol": "LP"
}
}增发代币(mint)
向指定用户账户增发代币并记入流水。
POST /tokens/:tokenId/mint路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
toUserId | string | 是 | 接收用户 ID |
amount | string | 是 | 增发数量 |
memo | string | 否 | 备注 |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/mint" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"toUserId": "507f1f77bcf86cd799439015",
"amount": "1000",
"memo": "Welcome bonus"
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439040",
"tokenId": "507f1f77bcf86cd799439020",
"type": "mint",
"amount": "1000",
"toUserId": "507f1f77bcf86cd799439015",
"memo": "Welcome bonus",
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Tokens minted successfully"
}销毁代币(burn)
从指定用户余额中销毁代币。
POST /tokens/:tokenId/burn路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
fromUserId | string | 是 | 销毁来源用户 ID |
amount | string | 是 | 销毁数量 |
memo | string | 否 | 备注 |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/burn" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"fromUserId": "507f1f77bcf86cd799439015",
"amount": "500",
"memo": "Redemption for reward"
}'响应示例:
{
"success": true,
"data": {
"_id": "507f1f77bcf86cd799439041",
"tokenId": "507f1f77bcf86cd799439020",
"type": "burn",
"amount": "500",
"fromUserId": "507f1f77bcf86cd799439015",
"memo": "Redemption for reward",
"createdAt": "2024-02-01T12:00:00.000Z"
},
"message": "Tokens burned successfully"
}调整用户余额
在公司管理池与指定用户之间划拨或收回代币。
POST /tokens/:tokenId/adjust路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
amount | string | 是 | 调整数量 |
operation | string | 是 | send(拨给用户)或 recall(收回) |
reason | string | 是 | 调整原因(审计用) |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/adjust" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015",
"amount": "100",
"operation": "send",
"reason": "Manual adjustment for customer service"
}'响应示例:
{
"success": true,
"data": {
"userBalance": "1100",
"adminBalance": "8900",
"transaction": {
"_id": "507f1f77bcf86cd799439042",
"tokenId": "507f1f77bcf86cd799439020",
"type": "transfer",
"amount": "100"
}
},
"message": "Balance sent"
}到期行为: 发放代币时,接收方分组的到期日继承来源分组在增发(mint)时定义的到期规则;发放与收回流程中不可单独覆盖到期日。从用户收回时,回到公司池的分组继承用户侧分组到期信息,以保持账本与可发放余额一致。
向用户发放代币
从公司代币池向用户发放;等价于 adjust 且 operation 固定为 send 的简化接口。
POST /tokens/:tokenId/send路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 目标用户 ID |
amount | string | 是 | 发放数量 |
reason | string | 否 | 发放原因 |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/send" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015",
"amount": "1000",
"reason": "Loyalty reward"
}'响应示例:
{
"success": true,
"data": {
"userBalance": "2000",
"adminBalance": "8000",
"transaction": {
"_id": "507f1f77bcf86cd799439043",
"tokenId": "507f1f77bcf86cd799439020",
"type": "transfer",
"amount": "1000"
}
},
"message": "Tokens sent to user"
}错误响应:
| HTTP 状态 | 错误码 | 说明 |
|---|---|---|
| 400 | VALIDATION_ERROR | 公司池余额不足,无法完成发放 |
| 404 | NOT_FOUND | 代币或用户不存在 |
从用户收回代币
将用户余额中的代币收回至公司池;等价于 adjust 且 operation 固定为 recall。
POST /tokens/:tokenId/recall路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
tokenId | string | 是 | 代币 ID |
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 收回来源用户 ID |
amount | string | 是 | 收回数量 |
reason | string | 否 | 收回原因 |
请求示例:
curl -X POST "https://your-domain.com/api/external/v1/tokens/507f1f77bcf86cd799439020/recall" \
-H "X-API-Key: vio_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"userId": "507f1f77bcf86cd799439015",
"amount": "500",
"reason": "Balance correction"
}'响应示例:
{
"success": true,
"data": {
"userBalance": "1500",
"adminBalance": "8500",
"transaction": {
"_id": "507f1f77bcf86cd799439044",
"tokenId": "507f1f77bcf86cd799439020",
"type": "transfer",
"amount": "500"
}
},
"message": "Tokens recalled from user"
}错误响应:
| HTTP 状态 | 错误码 | 说明 |
|---|---|---|
| 400 | VALIDATION_ERROR | 用户余额不足,无法收回 |
| 404 | NOT_FOUND | 代币或用户不存在 |
列举代币流水
分页查询代币转账、增发、销毁等流水记录。
GET /tokens/transactions/list查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | integer | 否 | 页码(默认 1) |
limit | integer | 否 | 每页条数(默认 20) |
tokenId | string | 否 | 按代币 ID 筛选 |
type | string | 否 | 按类型:mint、transfer、burn、reward、redeem |
fromDate | datetime | 否 | 起始时间 |
toDate | datetime | 否 | 结束时间 |
search | string | 否 | 按用户姓名或流水备注搜索 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/tokens/transactions/list?type=mint&limit=10" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"_id": "507f1f77bcf86cd799439040",
"tokenId": "507f1f77bcf86cd799439020",
"type": "mint",
"amount": "1000",
"toUserId": "507f1f77bcf86cd799439015",
"memo": "Welcome bonus",
"createdAt": "2024-02-01T12:00:00.000Z",
"token": {
"name": "Loyalty Points",
"symbol": "LP"
},
"toUser": {
"displayName": "John Doe",
"email": "john@example.com"
}
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 500,
"totalPages": 50,
"hasNextPage": true,
"hasPrevPage": false
}
}用户代币余额(/tokens/balance/user/:userId)
与用户章节中 /users/:userId/balances 数据一致,仅为不同入口。
GET /tokens/balance/user/:userId路径参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | string | 是 | 用户 ID |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/tokens/balance/user/507f1f77bcf86cd799439015" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": [
{
"tokenId": "507f1f77bcf86cd799439020",
"tokenName": "Loyalty Points",
"symbol": "LP",
"balance": "1500",
"lockedBalance": "0"
},
{
"tokenId": "507f1f77bcf86cd799439021",
"tokenName": "Reward Coins",
"symbol": "RC",
"balance": "250",
"lockedBalance": "50"
}
]
}10. API 参考:分析(Analytics)
所需 Scope: analytics
分析总览
返回租户用户、礼券、交易等汇总指标。
GET /analytics/overview请求示例:
curl -X GET "https://your-domain.com/api/external/v1/analytics/overview" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"users": {
"total": 5000,
"active": 4500,
"newThisMonth": 250
},
"vouchers": {
"totalClaims": 12000,
"redeemed": 8500,
"active": 3500,
"redemptionRate": "70.83%"
},
"transactions": {
"total": 25000,
"thisMonth": 3500
},
"generatedAt": "2024-02-01T12:00:00.000Z"
}
}礼券分析
按时间范围返回礼券领取/核销趋势及 Top 礼券等。
GET /analytics/vouchers查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
fromDate | datetime | 否 | 统计开始时间 |
toDate | datetime | 否 | 统计结束时间 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/analytics/vouchers?fromDate=2024-01-01T00:00:00.000Z&toDate=2024-01-31T23:59:59.000Z" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"totalVouchers": 50,
"totalClaims": 1200,
"totalRedemptions": 850,
"redemptionRate": "70.83%",
"claimsByDay": [
{ "date": "2024-01-01", "count": 45 },
{ "date": "2024-01-02", "count": 52 }
],
"redemptionsByDay": [
{ "date": "2024-01-01", "count": 30 },
{ "date": "2024-01-02", "count": 38 }
],
"topVouchers": [
{
"voucherId": "507f1f77bcf86cd799439011",
"name": "20% Off Discount",
"claims": 250,
"redemptions": 180
}
]
}
}用户分析
按时间范围返回新增用户、活跃用户数及按角色分布等。
GET /analytics/users查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
fromDate | datetime | 否 | 统计开始时间 |
toDate | datetime | 否 | 统计结束时间 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/analytics/users?fromDate=2024-01-01T00:00:00.000Z&toDate=2024-01-31T23:59:59.000Z" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"summary": {
"totalUsers": 5000,
"newUsersInPeriod": 500,
"activeUsers": 4500
},
"byRole": {
"member": 4800,
"sub_company_admin": 150,
"tenant_admin": 50
},
"dailySignups": [
{ "date": "2024-01-01", "count": 15 },
{ "date": "2024-01-02", "count": 22 }
]
}
}代币分析
按时间范围返回代币流水汇总、按类型分布等。
GET /analytics/tokens查询参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
fromDate | datetime | 否 | 统计开始时间 |
toDate | datetime | 否 | 统计结束时间 |
请求示例:
curl -X GET "https://your-domain.com/api/external/v1/analytics/tokens?fromDate=2024-01-01T00:00:00.000Z&toDate=2024-01-31T23:59:59.000Z" \
-H "X-API-Key: vio_live_your_api_key_here"响应示例:
{
"success": true,
"data": {
"summary": {
"totalTransactions": 3500,
"totalMinted": "500000",
"totalBurned": "150000",
"netChange": "350000"
},
"dailyTransactions": [
{ "date": "2024-01-01", "count": 120, "volume": "15000" },
{ "date": "2024-01-02", "count": 145, "volume": "18500" }
],
"byType": {
"mint": 800,
"transfer": 2000,
"burn": 400,
"reward": 200,
"redeem": 100
}
}
}11. 数据模型(Data Models)
以下模型与 API JSON 字段名一致;约束/格式列保留与校验规则相关的原文术语(如 ISO 8601、ObjectId),说明列为中文。
Voucher(礼券模板)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId(24 位十六进制) | 唯一 ID |
name | string | 至少 1 字符,已 trim | 展示名称 |
description | string | 可为空串 "",无最大长度 | 说明(纯文本) |
value | number | >= 0,默认 0 | 面额或折扣数值,含义由 valueType 决定 |
valueType | string | 枚举:fixed、percentage,默认 fixed | fixed 为固定金额,percentage 为折扣比例 |
valueCurrency | string | ISO 4217(如 THB、HKD),默认 THB | 固定金额类礼券的币种 |
voucherType | string | 枚举:cash、discount、product、cash_discount | 礼券业务类型 |
consumptionType | string | 枚举:vio_code、coupon_code、url、qr_code、manual、zhichong | 礼券核销交互方式 |
terms | string | 可为空串 | 条款 |
images | string[] | 每项为合法 URL,可为 [] | 配图,首张为主图 |
isActive | boolean | 默认 true | 是否启用、可领取 |
isTransferable | boolean | 默认 false | 领取后是否允许转让 |
totalQuantity | integer | -1 表示不限量;否则 >= 0,默认 -1 | 可领取总量 |
claimedQuantity | integer | >= 0,默认 0 | 已领取次数 |
maxClaimsPerUser | integer | 0 表示每用户不限;否则 >= 1,默认 1 | 单用户领取上限 |
startDate | string | ISO 8601,默认创建时间 | 生效时间 |
endDate | string | ISO 8601;null 表示不设到期 | 失效时间 |
visibility | string | 枚举:private、public、shared,默认 private | 可见范围,亦影响用户间转让规则 |
category | string | 枚举:Wellness、Health、Food & Beverage、Leisure & Entertainment、Travel & Hospitality、Lifestyle & Services、Others;可空 | 礼券主分类 |
categories | string[] | 字符串数组,每项已 trim | 分类标签 |
minSpend | number | >= 0,默认 0 | 最低消费门槛;0 表示无门槛 |
maxDiscount | number | 若设置须 > 0;可空 | 折扣封顶(比例类礼券常用) |
settlementAmount | number | >= 0,默认 0 | 跨租户结算金额 |
settlementCurrency | string | ISO 4217,默认 THB | 结算币种 |
externalProvider | string | 供货方代码;可空 | 外部供货方,如 vouchain |
externalId | string | 供货方模板 ID;可空 | 外部供货模板 ID |
externalRequiresDirectOrderParams | boolean | 默认 false | 外部核销是否需要额外账号参数 |
applicableScope | string | 枚举:all_outlets、partial_outlets、single_store | 适用门店范围 |
bookingEnabled | boolean | 默认 false | 是否启用预约 |
bookingDaysInAdvance | integer | >= 0,默认 0 | 可提前预约天数 |
createdAt | string | ISO 8601,自动生成 | 创建时间 |
updatedAt | string | ISO 8601,自动更新 | 最近更新时间 |
VoucherClaim(UserVoucher,用户礼券领取记录)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId | 唯一 ID |
voucherId | string | MongoDB ObjectId | 关联的礼券模板 ID |
userId | string | MongoDB ObjectId | 所属用户 ID |
status | string | 枚举:active、claimed、redeemed、expired,默认 active | 领取记录状态 |
redemptionCode | string | 唯一;格式如 VCH-{base36时间戳}-{4位随机} | 核销码 |
claimedAt | string | ISO 8601,默认当前时间 | 领取时间 |
redeemedAt | string | ISO 8601;未核销前为 null | 核销时间 |
expiresAt | string | ISO 8601;null 为不设到期;一般继承模板 endDate | 本条记录到期时间 |
redemptionDetails.location | string | 可选 | 核销地点 |
redemptionDetails.notes | string | 可选 | 核销备注 |
redemptionDetails.method | string | 可选 | 核销方式,如 api_key 或 pin |
externalVoucherCode | string | 可空 | 外部供货返回的券码 |
externalRedemptionUrl | string | 可空 | 外部供货返回的核销链接 |
externalOrderId | string | 可空 | 外部供货订单 ID |
externalFulfillmentStatus | string | 枚举:pending、processing、fulfilled、failed、null | 外部履约状态 |
User(用户)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId | 唯一 ID |
email | string | 邮箱格式;未设置为 null;email 与 phone 至少其一有值 | 邮箱 |
phone | string | 手机号格式;未设置为 null | 手机 |
displayName | string | 未设置为 null,已 trim | 展示名 |
avatar | string | 合法 URL;未设置为 null | 头像 |
role | string | 枚举:member、sub_company_admin、tenant_admin、super_admin | 角色 |
isActive | boolean | 默认 true | 是否启用 |
walletAddress | string | 以太坊地址 0x…;创建时自动分配 | 托管钱包地址 |
registrationSource | string | 枚举:created、direct、store、campaign | 注册来源 |
storeId | object | 可能被 populate;含 _id、name 等;否则 null | 门店注册场景 |
campaignId | object | 可能被 populate;含 _id、name、slug 等;否则 null | 活动注册场景 |
metadata | object | 任意键值 | 自定义元数据 |
createdAt | string | ISO 8601 | 注册时间 |
updatedAt | string | ISO 8601 | 最近更新时间 |
Campaign(活动)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId | 唯一 ID |
name | string | 至少 1 字符 | 活动名称 |
description | string | 可为空串 | 活动说明 |
slug | string | 小写 URL 安全;租户+子公司内唯一;可由 name 生成 | 活动页路径标识 |
tokenId | string | MongoDB ObjectId,必填 | 用户领取礼券时消耗的代币 |
isActive | boolean | 默认 true | 是否启用;停用则对用户隐藏 |
isPublic | boolean | 默认 true | 是否公开(免登录可访问) |
startDate | string | ISO 8601,默认创建时间 | 开始时间 |
endDate | string | ISO 8601;null 为不设结束 | 结束时间 |
images | string[] | 每项为合法 URL,可为 [] | 横幅/配图 |
createdAt | string | ISO 8601 | 创建时间 |
Token(代币)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId | 唯一 ID |
name | string | 已 trim | 名称 |
symbol | string | 通常 2–5 位大写 | 符号(如 LP) |
description | string | 可为空串 | 说明 |
totalSupply | string | 数字字符串,精度由业务定义 | 总供应量 |
decimals | integer | >= 0,默认 0 | 小数位 |
isActive | boolean | 默认 true | 是否启用 |
createdAt | string | ISO 8601 | 创建时间 |
updatedAt | string | ISO 8601 | 更新时间 |
TokenBalance(代币余额)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
tokenId | string | MongoDB ObjectId | 代币 ID |
tokenName | string | 已 trim | 代币名称 |
symbol | string | 通常 2–5 位大写 | 符号 |
balance | string | 数字字符串,>= "0" | 可用余额 |
lockedBalance | string | 数字字符串,>= "0" | 锁定余额 |
Transaction(交易流水)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
_id | string | MongoDB ObjectId | 流水 ID |
tokenId | string | MongoDB ObjectId | 代币 ID |
type | string | 枚举:mint、transfer、burn、reward、redeem、expire | 类型 |
amount | string | 数字字符串,> "0" | 金额 |
fromUserId | string | MongoDB ObjectId;增发场景为 null | 转出用户 |
toUserId | string | MongoDB ObjectId;销毁场景为 null | 转入用户 |
memo | string | 未提供为 null | 备注/原因 |
createdAt | string | ISO 8601 | 发生时间 |
Pagination(分页元数据)
| 字段 | 类型 | 约束 / 格式 | 说明 |
|---|---|---|---|
page | integer | >= 1 | 当前页(从 1 起) |
limit | integer | 1–100 | 每页条数 |
total | integer | >= 0 | 符合条件的总数 |
totalPages | integer | >= 0 | 总页数 |
hasNextPage | boolean | true/false | 是否有下一页 |
hasPrevPage | boolean | true/false | 是否有上一页 |
12. 代码示例(Code Examples)
以下为可运行示例:路径与 JSON 字段名与线上一致,便于直接复制;示例代码内注释仍为英文不影响运行。
JavaScript / Node.js 示例
// VIO API Client Example
const VIO_API_BASE = "https://your-domain.com/api/external/v1";
const API_KEY = "vio_live_your_api_key_here";
// Helper function for API calls
async function vioApi(method, endpoint, body = null) {
const options = {
method,
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${VIO_API_BASE}${endpoint}`, options);
const data = await response.json();
if (!data.success) {
throw new Error(data.error?.message || "API request failed");
}
return data;
}
// Example: Create a voucher
async function createVoucher() {
const result = await vioApi("POST", "/vouchers", {
name: "Welcome Discount",
description: "10% off your first purchase",
value: 10,
valueType: "percentage",
totalQuantity: 1000,
maxClaimsPerUser: 1,
visibility: "public",
endDate: "2024-12-31T23:59:59.000Z",
});
console.log("Created voucher:", result.data._id);
return result.data;
}
// Example: Mint tokens to a user
async function mintTokensToUser(tokenId, userId, amount) {
const result = await vioApi("POST", `/tokens/${tokenId}/mint`, {
toUserId: userId,
amount: amount.toString(),
memo: "Reward for purchase",
});
console.log("Minted tokens:", result.data);
return result.data;
}
// Example: Redeem a voucher by code
async function redeemVoucher(redemptionCode, location) {
const result = await vioApi("POST", "/vouchers/redeem-by-code", {
redemptionCode,
location,
notes: "Redeemed at checkout",
});
console.log("Voucher redeemed:", result.data);
return result.data;
}
// Example: Consume a voucher with staff PIN
async function consumeVoucherByPin(redemptionCode, staffPin) {
const result = await vioApi(
"POST",
`/vouchers/redeem/${redemptionCode}/pin`,
{
pin: staffPin, // e.g., 'HA1234'
},
);
console.log("Voucher consumed by:", result.data.redeemedBy);
return result.data;
}
// Example: Get user with their balances
async function getUserWithBalances(userId) {
const [userResult, balancesResult] = await Promise.all([
vioApi("GET", `/users/${userId}`),
vioApi("GET", `/users/${userId}/balances`),
]);
return {
user: userResult.data,
balances: balancesResult.data,
};
}
// Example: Search for a user by email
async function findUserByEmail(email) {
const result = await vioApi(
"GET",
`/users/search/by-identifier?email=${encodeURIComponent(email)}`,
);
return result.data;
}Python 示例
import requests
from typing import Optional, Dict, Any
VIO_API_BASE = 'https://your-domain.com/api/external/v1'
API_KEY = 'vio_live_your_api_key_here'
def vio_api(method: str, endpoint: str, body: Optional[Dict] = None) -> Dict[str, Any]:
"""Make a request to the VIO API."""
headers = {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
}
url = f'{VIO_API_BASE}{endpoint}'
if method == 'GET':
response = requests.get(url, headers=headers)
elif method == 'POST':
response = requests.post(url, headers=headers, json=body)
elif method == 'PATCH':
response = requests.patch(url, headers=headers, json=body)
elif method == 'DELETE':
response = requests.delete(url, headers=headers)
else:
raise ValueError(f'Unsupported method: {method}')
data = response.json()
if not data.get('success'):
error = data.get('error', {})
raise Exception(error.get('message', 'API request failed'))
return data
# Example: List active vouchers
def list_vouchers(page: int = 1, limit: int = 20):
result = vio_api('GET', f'/vouchers?page={page}&limit={limit}&isActive=true')
return result['data'], result['pagination']
# Example: Create a user
def create_user(email: str, password: str, display_name: str):
result = vio_api('POST', '/users', {
'email': email,
'password': password,
'displayName': display_name,
'role': 'member',
})
return result['data']
# Example: Get analytics overview
def get_analytics_overview():
result = vio_api('GET', '/analytics/overview')
return result['data']
# Usage
if __name__ == '__main__':
# List vouchers
vouchers, pagination = list_vouchers()
print(f'Found {pagination["total"]} vouchers')
# Get analytics
analytics = get_analytics_overview()
print(f'Total users: {analytics["users"]["total"]}')完整流程:创建礼券并跟踪领取
#!/bin/bash
# 使用 cURL 的完整流程示例
API_BASE="https://your-domain.com/api/external/v1"
API_KEY="vio_live_your_api_key_here"
# 1. 新建礼券
echo "Creating voucher..."
VOUCHER_RESPONSE=$(curl -s -X POST "$API_BASE/vouchers" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Flash Sale 25% Off",
"description": "Limited time offer - 25% discount",
"value": 25,
"valueType": "percentage",
"totalQuantity": 100,
"maxClaimsPerUser": 1,
"visibility": "public",
"startDate": "2024-02-01T00:00:00.000Z",
"endDate": "2024-02-28T23:59:59.000Z"
}')
VOUCHER_ID=$(echo $VOUCHER_RESPONSE | jq -r '.data._id')
echo "Created voucher: $VOUCHER_ID"
# 2. 查询礼券详情
echo "Fetching voucher details..."
curl -s -X GET "$API_BASE/vouchers/$VOUCHER_ID" \
-H "X-API-Key: $API_KEY" | jq
# 3. 列出该礼券的领取记录
echo "Listing claims..."
curl -s -X GET "$API_BASE/vouchers/claims/list?voucherId=$VOUCHER_ID" \
-H "X-API-Key: $API_KEY" | jq
# 4. 顾客出示核销码时先查询
REDEMPTION_CODE="ABC123XYZ"
echo "Looking up redemption code..."
curl -s -X GET "$API_BASE/vouchers/redeem/$REDEMPTION_CODE/info" \
-H "X-API-Key: $API_KEY" | jq
# 5. 核销礼券
echo "Redeeming voucher..."
curl -s -X POST "$API_BASE/vouchers/redeem-by-code" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"redemptionCode\": \"$REDEMPTION_CODE\",
\"location\": \"Main Store\",
\"notes\": \"Customer order #12345\"
}" | jq
# 6. 礼券维度分析数据
echo "Fetching voucher analytics..."
curl -s -X GET "$API_BASE/analytics/vouchers" \
-H "X-API-Key: $API_KEY" | jq13. 更新日志(Changelog)
版本 1.1.0(2026 年 4 月)
注册来源、活动分析与 PIN 权限
- User 模型增加
registrationSource(created、direct、store、campaign) - User 响应中
storeId、campaignId可能为 populate 后的对象引用 - 增加活动访问与报名等分析统计
- 新增公开接口
POST /api/campaigns/:id/track-visit用于活动页访问统计 - 活动列表项包含
analytics对象,含visits、registrations等计数 - 核销 PIN 增加
permissions字段,可取voucher_redemption、token_claim等 - 每个 PIN 可配置一项或多项权限,决定可授权的操作类型
POST /api/public/verify-pin响应中增加permissions数组- 店员 PIN 核销礼券(
POST /vouchers/redeem/:code/pin)要求 PIN 具备voucher_redemption权限 - 历史上未显式配置权限的 PIN 默认视为拥有
voucher_redemption,以保持兼容
版本 1.0.0(2024 年 2 月)
首次发布
- 礼券完整 CRUD
- 用户完整 CRUD
- 活动完整 CRUD
- 代币管理(列表、增发、销毁、调整)
- 礼券、用户、代币分析接口
- API Key 鉴权与 scope
- 限流(每分钟 60 次、每日 10,000 次)
- 支持 API Key IP 白名单
需要帮助?
- 管理后台:在 设置 > API Keys 创建与管理 API Key
- Swagger UI:交互式文档位于
/api-docs - 支持:请联系 VIO 技术支持
本文档对应 VIO External API v1。最后更新:2026 年 3 月。