VoucherVault 1.30.3 API 使用详解

VoucherVault v1.30.3 REST API 实测详解:唯一统计端点 /api/get/stats 的 Bearer Token 认证、完整响应字段口径、按用户过滤、Home Assistant 接入示例与排障实录(含 v1.30.3 未知用户 500 bug)。

VoucherVaultVoucherVaultDjango优惠券管理2026/10/25

VoucherVault 1.30.3 API 使用详解

目标:讲清 VoucherVault v1.30.3 REST API 的能力边界、认证方式、调用方法与响应字段,并给出 Home Assistant 接入示例。全部请求与回显来自 2026-10-25 的真实环境(安装方式见《Ubuntu 22.04 安装 VoucherVault 指南》)。

一、环境信息

项目值
软件版本VoucherVault v1.30.3(Django 5.2.18)
API 基础路径http://<host>:<port>/en/api/(语言前缀 /en/ 不可省略)
认证方式HTTP Header Authorization: Bearer <api_token>
端点数量1 个(只读统计端点,详见下文)
数据格式JSON(Content-Type: application/json)

二、能力边界:先看清楚能做什么

VoucherVault 的 API 只有一个端点:

GET /en/api/get/stats

它返回库内卡券的聚合统计与明细,设计目标是给 Home Assistant 等外部仪表盘做数据展示。没有卡券的增删改查写接口,也没有用户管理接口——所有写操作必须在 Web 界面完成。需要完整读写 API 的场景请评估 OfferKit 类促销引擎(对比见《VoucherVault 介绍与选型指南》)。

三、获取与管理 API Token

Token 存于数据库 AppSettings 单例模型的 api_token 字段(UUID 格式),不是用户属性,全实例共用一个 Token。

方式一:Django 管理后台查看/重新生成(推荐)

  1. 用 admin 账号登录 http://<host>:<port>/en/admin/;
  2. 进入「MYAPP → App settings」;
  3. 列表页可见当前 Token,并有「Regenerate token」操作可一键轮换(轮换后旧 Token 立即失效)。

方式二:命令行查看(服务器本地排障用)

裸机形态:

cd /opt/vouchervault/app
runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py shell -c \
  "from myapp.models import AppSettings; print(AppSettings.objects.last().api_token)"
f4597b50-79d6-43e6-9101-c5d0bf5bfda2

⚠️ 若查询结果为 None(新装实例从未保存过 App settings),说明 Token 尚未生成。登录管理后台打开一次 App settings 保存即可,或用 shell 执行 AppSettings.objects.create() 触发默认 UUID 生成。

四、调用实录

以下示例 Token 为 f4597b50-79d6-43e6-9101-c5d0bf5bfda2(测试实例,环境已销毁)。

4.1 不带 Token → 403

curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:8000/en/api/get/stats
Unauthorized. Invalid or missing authorization token.
HTTP 403

4.2 错误 Token → 403

curl -s -w '\nHTTP %{http_code}\n' \
  -H 'Authorization: Bearer wrong-token-123' \
  http://127.0.0.1:8000/en/api/get/stats
Unauthorized. Invalid or missing authorization token.
HTTP 403

403 响应体是纯文本,不是 JSON——客户端做错误处理时注意区分。

4.3 正确 Token → 200

测试库中预置了 4 张卡券(代金券/礼品卡/优惠券/会员卡各一,礼品卡含两笔消费记录):

curl -s -H 'Authorization: Bearer f4597b50-79d6-43e6-9101-c5d0bf5bfda2' \
  http://127.0.0.1:8000/en/api/get/stats | python3 -m json.tool
{
    "item_stats": {
        "total_items": 4,
        "total_value": "139.50",
        "vouchers": 1,
        "giftcards": 1,
        "coupons": 1,
        "loyaltycards": 1,
        "used_items": 0,
        "available_items": 4,
        "expired_items": 0,
        "soon_expiring_items": 1
    },
    "item_details": [
        {
            "id": "544b4194-ddba-4ba5-9a97-509ee004e759",
            "type": "voucher",
            "name": "Amazon Gift Voucher",
            "redeem_code": "AMZ-2026-ABCD-EFGH",
            "code_type": "qrcode",
            "pin": null,
            "issuer": "Amazon",
            "value": "50.00",
            "value_type": "money",
            "currency": "EUR",
            "issue_date": "2026-10-25",
            "expiry_date": "2027-01-23",
            "description": "Birthday gift card",
            "is_used": false,
            "is_pinned": false,
            "user__username": "admin"
        },
        {
            "id": "35d32bf0-54da-49d2-875a-803602db7f53",
            "type": "giftcard",
            "name": "Starbucks Card",
            "redeem_code": "SBUX-1234-5678",
            "code_type": "barcode",
            "pin": null,
            "issuer": "Starbucks",
            "value": "100.00",
            "value_type": "money",
            "currency": "EUR",
            "issue_date": "2026-10-25",
            "expiry_date": "2027-04-23",
            "description": "Coffee prepaid card",
            "is_used": false,
            "is_pinned": false,
            "user__username": "admin"
        }
    ],
    "issuer_stats": [
        {"issuer": "Starbucks", "count": 1, "total_value": "79.50"},
        {"issuer": "LocalMart", "count": 1, "total_value": "10.00"},
        {"issuer": "IKEA", "count": 1, "total_value": "0.00"},
        {"issuer": "Amazon", "count": 1, "total_value": "50.00"}
    ],
    "user_stats": {
        "total_users": 1,
        "active_users": 1,
        "disabled_users": 0,
        "superusers": 1,
        "staff_members": 1
    }
}

(为节省篇幅,item_details 只展示了前 2 条,实际返回全部 4 条。)

4.4 按用户过滤

?user=<username> 只统计某个用户的卡券:

curl -s -H 'Authorization: Bearer f4597b50-79d6-43e6-9101-c5d0bf5bfda2' \
  'http://127.0.0.1:8000/en/api/get/stats?user=admin'

返回结构相同,数据集收缩到该用户名下。

五、响应字段详解

5.1 item_stats(聚合统计)

字段含义计算口径(源码核对)
total_items卡券总数全量计数
total_value有效卡券净值合计仅统计「未使用且未过期」:面值 + 交易流水合计(消费为负数)
vouchers / giftcards / coupons / loyaltycards四类卡券各自数量按 type 分组计数
used_items已核销数量is_used=True
available_items可用数量未使用 − 已过期
expired_items已过期数量expiry_date < now
soon_expiring_items临期数量未过期但距到期 < EXPIRY_THRESHOLD_DAYS(默认 30 天,配置见《VoucherVault 配置参数详解》)

实测验证净值算法:Starbucks 卡面值 100.00,两笔消费 −12.50、−8.00,净值 79.50;合计 50.00 + 79.50 + 10.00 + 0.00 = 139.50,与返回一致。

5.2 item_details(卡券明细)

字段含义
id卡券 UUID
typevoucher / giftcard / coupon / loyaltycard
redeem_code / code_type / pin核销码、码制(qrcode/barcode/code128 等)、PIN
issuer发行方
value / value_type / currency面值、面值类型(money/percentage/multiplier)、币种
issue_date / expiry_date发行/到期日期(ISO 格式)
is_used / is_pinned是否已核销、是否置顶
user__username属主用户名(Django ORM 双下划线字段直接外暴露)

⚠️ 注意:redeem_code 明文出现在响应里,该 API 等同于卡券数据导出通道,Token 必须按敏感凭据管理。

5.3 issuer_stats(发行方聚合)

按 issuer 分组的 count 与 total_value(同样含交易净值口径)。无固定排序(实测返回顺序与字母序无关),消费方如需排序自行处理。

5.4 user_stats(用户聚合)

实例级用户计数:总数、激活数、禁用数、超户数、staff 数。与 ?user= 过滤无关,始终返回全实例数据。

六、接入 Home Assistant

官方设计场景就是把统计挂到 HA 仪表盘。在 configuration.yaml 中配置 RESTful Sensor:

rest:
  - resource: http://192.168.1.10:8130/en/api/get/stats
    headers:
      Authorization: "Bearer f4597b50-79d6-43e6-9101-c5d0bf5bfda2"
    scan_interval: 3600
    sensor:
      - name: "VoucherVault 临期卡券"
        value_template: "{{ value_json.item_stats.soon_expiring_items }}"
        unit_of_measurement: "张"
        json_attributes_path: "$.item_stats"
        json_attributes:
          - total_items
          - total_value
          - available_items
          - expired_items

要点:

  • scan_interval 建议 3600 秒,到期提醒本就是天级粒度;
  • 更多维度(如按发行方)可用 template 传感器解析 json_attributes;
  • 生产环境务必走 HTTPS,避免 Token 与核销码明文过网。

七、排障实录

1. ?user= 传了不存在的用户名,返回 500 而非预期的 404

实测回显:

{"error": "An unexpected error occurred.", "details": "name 'ObjectDoesNotExist' is not defined"}
HTTP 500

这是 v1.30.3 的真实 bug:views.py 的 get_stats 里 except ObjectDoesNotExist: 捕获了一个从未 import 的异常类,导致用户不存在时抛 NameError 走通用 500 分支。对接侧按「500 且 details 含 ObjectDoesNotExist 即用户不存在」兼容处理,或等待上游修复。

2. 请求路径少了语言前缀返回 404

端点注册在 i18n_patterns 内,/api/get/stats 不带 /en/ 会 404。固定写 /en/api/get/stats 即可(不接受 Accept-Language 协商)。

3. Token 复制后仍 403

确认 Header 格式严格为 Authorization: Bearer <uuid>(Bearer 与 Token 之间一个空格);确认没有在管理后台误点 Regenerate token 导致 Token 已轮换。

4. total_value 与卡券面值之和对不上

total_value 是净值:扣除了礼品卡交易流水,且不含已使用与已过期的卡券。对账时先按 item_details 逐条筛 is_used=false 且未过期再加总。

八、参考链接