关于 EVS Meter Tools
EVS Meter Tools 是面向 NUS 和 SUTD 部分电表的网页应用与 Telegram 机器人,可查询余额、用量和充值记录,并在支持时通过银行卡充值。其只读 MCP 服务让 AI 助手查询电表信息并提供付款链接,不处理付款或银行卡信息。
主要功能
- 01查询 NUS 和 SUTD 电表余额
- 02查看用量与近期充值记录
- 03银行卡充值链接
- 04Telegram 机器人与网页应用
- 05只读电表工具
- 06MCP 不接收银行卡信息
适用场景
- 01查询 NUS 或 SUTD 电表余额
- 02查看近期用量和充值记录
- 03获取支持电表的充值链接
- 04让 AI 助手检查余额是否偏低
MCP 相关
公开的只读 MCP 服务提供查询电表余额、NUS 用量、近期充值记录和充值链接的工具。它不能进行付款或接收银行卡信息,且响应不包含房间地址。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://nusaircon-production.up.railway.app/mcpclaude mcp add --transport http nicholasohjj-nusaircon https://nusaircon-production.up.railway.app/mcp{"mcpServers":{"nicholasohjj-nusaircon":{"type":"http","url":"https://nusaircon-production.up.railway.app/mcp"}}}在 Cursor 中打开{"servers":{"nicholasohjj-nusaircon":{"type":"http","url":"https://nusaircon-production.up.railway.app/mcp"}}}在 VS Code 中打开{"mcpServers":{"nicholasohjj-nusaircon":{"type":"http","url":"https://nusaircon-production.up.railway.app/mcp"}}}{"mcpServers":{"nicholasohjj-nusaircon":{"serverUrl":"https://nusaircon-production.up.railway.app/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
EVS 电表工具
面向受支持 EVS 电表的 Telegram 机器人和 Web 应用。受支持的 NUS 和 SUTD 电表可通过信用卡充值;在可用时,受支持的查询流程会显示余额、用量和充值历史。
支持的系统
| 宿舍组 | EVS 系统 |
|---|---|
| NUS 宿舍,包括 PGPR、Houses @ PGP、Residential Colleges、NUS College、UTown Residences、RVRC、Valour House | cp2nus.evs.com.sg |
| SUTD | SUTDMain / EVSSUTDWebPOS |
旧版 NUS CP2 备用方案(cp2.evs.com.sg)在 Web 应用和机器人中均不可见。其后端路由和服务仍保留,但未关联到任何 UI;请参阅 CP2 — 旧版 NUS 备用方案(隐藏)。
功能
- 在 Telegram 中查看电表余额、7 天用量历史和近期充值记录
- 通过信用卡为受支持的 NUS 电表充值(SGD $6-$50)
- 通过信用卡为受支持的 SUTD 电表充值(SGD $10-$50)
- 使用 RSA 加密输入银行卡信息——银行卡信息不会以明文形式离开浏览器
- 可作为 Telegram Mini App 使用,也可作为独立网站使用
- 跨系统防护:cp2nus 引导流程会在发起支付前拒绝属于旧版 cp2 系统的电表
- SUTD 余额、充值历史和充值支持
- 在整个流程中跟踪分析数据并捕获错误
- Telegram 低余额提醒(需主动开启),并提供开始充值的按钮
- 只读 MCP 服务,用户可连接自己的 Claude 并设置自己的指令
架构
Telegram Bot (telegraf) Website (React, /app/)
│ │
├── /topup └── HomePage
├── /balance └── system selection
├── /usage + meter ID + amount
└── /topups
│ webhook in production/Railway, │
│ polling in development │
▼ ▼
Express (server.js) ←────────────────────────────
│
├── POST /telegram/webhook/* — Telegram webhook receivers
├── GET /webapp — legacy CP2 (hidden from UI): fetches meter summary, redirects to React
├── GET /webapp/bootstrap — runs full payment init, returns token
├── GET /webapp/session — returns session data as JSON for React
├── GET /webapp/pay — redirects to React card entry page
├── POST /webapp/enets_pay — proxies encrypted card data to eNETS
├── POST /webapp/notify — fallback Telegram payment notification
└── GET /webapp/result — redirects to React result page
React Frontend (/app/)
├── / — HomePage (system + meter ID + amount)
├── /balance — HomePage opened in balance-check mode
├── /cp2nus — HomePage preselected for primary NUS CP2NUS flow
├── /cp2nus/loading — cp2nus variant
├── /cp2nus/pay — cp2nus variant
├── /cp2nus/result — cp2nus variant
├── /sutd/loading — SUTD variant
├── /sutd/pay — SUTD variant
└── /sutd/result — SUTD variant
支付流程
CP2 — 旧版 NUS 备用方案(隐藏)
隐藏。 此流程已不再通过 Web 应用或机器人提供。
frontend/src/App.jsx中的 React 路由/app/cp2、/app/loading、/app/pay和/app/result已注释掉,/app/cp2已从 sitemap 和 robots.txt 中排除,机器人也不再显示宿舍选择器。以下后端路由和服务仍予以保留,供参考或日后重新启用。
该流程会抓取旧版 EVS WebPOS 门户以创建交易,然后通过 eNETS 代理支付。它仅用于尚未迁移到 CP2NUS 的 NUS 电表。
/webapp— 从 ORE 获取电表地址和余额,并将地址和余额作为查询参数传递到 React 加载页面- 引导流程(
/webapp/bootstrap)— 并行运行runPurchaseFlow和getMeterSummary:GET /EVSWebPOS/→ 登录 →POST /loginServlet(电表验证)POST /selectOfferServlet(金额选择)GET /paymentServlet→ 提取merchant_txn_refPOST creditpayment.jsp→ 提取 eNETSmessagePOST /enets2/PaymentListener.do→ 提取 RSA 公钥(n、e)、netsMid、netsTxnRef- 创建支付会话(TTL 为 10 分钟),重定向到 React 银行卡页面
- 银行卡页面(
/app/pay)— React 组件;通过/webapp/session获取会话;在客户端使用 eNETS RSA 脚本加密cardNo + cvv - 支付代理(
/webapp/enets_pay):POST https://www.enets.sg/GW2/uCredit/pay- **首选路径:**提取 EVS 回调表单 →
POST /EVSWebPOS/transSumServlet→parseEvsTransactionSummary - **备用方案:**直接抓取 eNETS 收据 HTML,由
parseEnetsResult解析 - 将结果(
status、merchantTxnRef、reason)写回服务端会话
- 结果页面(
/app/result)— React 组件;通过/webapp/session从服务端会话读取结果
CP2NUS — NUS 宿舍主流程
直接使用 EVS JSON API 和 eNETS Payment Page(enetspp)主机。这是适用于已迁移电表的 NUS 宿舍主流程。
/webapp— 与 cp2 相同;获取电表信息,并重定向到/app/cp2nus/loading- 引导流程(
/cp2nus/webapp/bootstrap)—runBootstrap按顺序运行:- 电表系统检查 —
isCp2Meter()守卫:拒绝 cp2 电表并返回WRONG_SYSTEM错误;网络故障时跳过检查并继续流程 init_pay—POST /enets/init_pay→{ txn_identifier, req, sign }meter_info—getMeterSummary→buildPayDisplayAddressenetspp_pay—buildEnetsPayUrl→GET enetspp/pay?p=…→ 提取txnReq、keyId、hmacTxnReqListener—POST /GW2/TxnReqListener→ RSA 密钥、netsTxnRef、netsMid、paymtNetsMid、txnRand、keyId、hmac
- 电表系统检查 —
- 银行卡页面(
/app/cp2nus/pay)— 使用相同的 RSA 加密;panSubmitForm使用paymtNetsMid(收单 MID),而非顶层的netsMid - 支付代理(
/cp2nus/webapp/enets_pay):GET /GW2/pluginpages/env.jsp→ 创建JSESSIONID种子POST /GW2/credit/init;jsessionid=…POST /GW2/credit/panSubmitForm- **首选路径:**响应中的
netsTxnStatus→preParsed结果(不调用 b2s) - 回退路径:
POST /enets/b2s→ 303 重定向 →parsePayResult对参数进行 base64 解码
- 结果页面(
/app/cp2nus/result)— 从服务端会话中读取结果
SUTD
使用 SUTD EVS WebPOS 页面和 SUTD 信用卡支付桥接服务。
/sutd/webapp— 获取 SUTD 电表余额,并重定向到/app/sutd/loading- 引导流程(
/sutd/webapp/bootstrap)— 运行 SUTD WebPOS 流程:- 使用电表 ID 执行
POST /EVSSUTDWebPOS/loginServlet - 使用选定金额执行
POST /EVSSUTDWebPOS/selectOfferServlet GET /EVSSUTDWebPOS/paymentServlet→ 提取merchant_txn_refPOST http://120.50.44.233/payment_sutd_credit/creditpayment.jspPOST /enets2/PaymentListener.do→ 提取 RSA 公钥、netsMid、netsTxnRef
- 使用电表 ID 执行
- 银行卡页面(
/app/sutd/pay)— 与 NUS 流程相同,使用 RSA 加密输入银行卡信息 - 支付代理(
/sutd/webapp/enets_pay)— 将加密后的银行卡数据提交到 eNETS,然后将回调提交到/EVSSUTDWebPOS/transSumServlet - 结果页面(
/app/sutd/result)— 从服务端会话中读取结果
设置
前置条件
- Node.js 20.19+(Node 22 需 22.12+)
- Telegram 机器人令牌(从 @BotFather 获取)
- 可公开访问的 HTTPS 服务器(Telegram WebApp 按钮必需)
安装
# Install backend dependencies
npm install
# Install and build the frontend
npm run build:frontend
环境变量
创建一个 .env 文件:
TELEGRAM_BOT_TOKEN=your_bot_token_here # legacy single-bot token
NUS_TELEGRAM_BOT_TOKEN=your_nus_bot_token # optional, for concurrent nusairconbot
SUTD_TELEGRAM_BOT_TOKEN=your_sutd_bot_token # optional, for concurrent sutdairconbot
PAYMENT_SESSION_SECRET=replace_with_openssl_rand_hex_32 # stable secret for signed payment/result tokens
SERVER_URL=https://your-public-server.example.com
OWNER_CHAT_ID=your_telegram_chat_id # receives feedback notifications
MAINTENANCE_MODE=false # set to "true" to put the app and bot in global maintenance mode
MAINTENANCE_MESSAGE= # optional custom global maintenance message
OWNER_STATUS_TIMEOUT_MS=4000 # optional timeout for owner /status EVS/eNETS checks
TOPUP_DISABLED=false # set to "true" to show maintenance message
DB_DIR=. # local SQLite dir; use /data on Railway with a mounted volume
TELEGRAM_BOT_MODE= # production/Railway defaults to webhook; dev defaults to polling
TELEGRAM_BOT_AUDIENCE=nus # legacy single-bot mode: "nus" or "sutd"
LOW_BALANCE_CRON_SECRET= # optional, enables the low-balance check endpoint
MCP_ALLOWED_ORIGINS= # optional, comma-separated extra browser origins allowed to call /mcp
GOOGLE_SITE_VERIFICATION_FILE= # optional Search Console HTML file name, e.g. googleabc123.html
GOOGLE_SITE_VERIFICATION_CONTENT= # optional exact file body if Google provides non-default content
SERVER_URL 必须使用 HTTPS,Telegram WebApp 付款按钮才能正常工作。如果使用 HTTP,机器人会改用普通浏览器链接。使用 Railway 时,将其设为 https://${{RAILWAY_PUBLIC_DOMAIN}},也可以留空,让应用从 RAILWAY_PUBLIC_DOMAIN 推导该值。
PAYMENT_SESSION_SECRET 应为稳定的随机值,例如使用 openssl rand -hex 32 生成。如果省略该变量,应用会回退使用已配置的机器人令牌;只要机器人令牌不变,即使 Railway 休眠,该值也会保持不变。
如需通过 Google Search Console 的网址前缀验证,请使用 HTML 文件验证方式,并将 GOOGLE_SITE_VERIFICATION_FILE 设为下载的文件名。默认响应正文为 google-site-verification: <file name>。如果 Google 下载的文件包含不同文本,请将 GOOGLE_SITE_VERIFICATION_CONTENT 设为文件正文的准确内容。
在生产环境中,或设置了 RAILWAY_PUBLIC_DOMAIN 时,每个已配置的机器人默认使用 Telegram Webhook,以便 Railway Serverless 可以休眠,并在收到 Telegram 请求时唤醒。只有在持续运行的部署中,才将 TELEGRAM_BOT_MODE=polling。单机器人模式下的可选 Webhook 变量为 TELEGRAM_WEBHOOK_PATH、TELEGRAM_WEBHOOK_SECRET 和 TELEGRAM_DROP_PENDING_UPDATES。对于并发机器人,只有需要自定义路径时才使用 NUS_TELEGRAM_WEBHOOK_PATH、NUS_TELEGRAM_WEBHOOK_SECRET、SUTD_TELEGRAM_WEBHOOK_PATH 和 SUTD_TELEGRAM_WEBHOOK_SECRET;否则会自动生成唯一的路径。
如需在同一次部署中运行多个 Telegram 机器人,请同时设置 NUS_TELEGRAM_BOT_TOKEN 和 SUTD_TELEGRAM_BOT_TOKEN。同一服务会启动 nusairconbot 和 sutdairconbot,两者分别使用独立的命令菜单、Webhook 路径、内存会话和付款通知。单机器人部署仍可使用 TELEGRAM_BOT_TOKEN 和 TELEGRAM_BOT_AUDIENCE。
运行
# Development (two terminals)
npm run dev # Express backend on :3000
npm run dev:frontend # Vite dev server on :5173
# Production
npm run build:frontend
npm start
在生产环境中,Express 会在 /app/ 提供前端。在开发环境中,Vite 会将 /webapp、/cp2nus 和 /sutd 代理到后端。
本地开发时,请保留 DB_DIR=. 或省略该变量。在 Railway 上,只有在挂载到 /data 的卷已就绪后,才将 DB_DIR=/data。
Railway Serverless
仓库中包含 railway.json,其中定义了生产构建、启动命令和 /health 检查。需要在控制面板中启用 Railway Serverless:
- 为服务添加公共域名。
- 对于单个机器人,设置
TELEGRAM_BOT_TOKEN;对于并发机器人,设置NUS_TELEGRAM_BOT_TOKEN和SUTD_TELEGRAM_BOT_TOKEN。同时设置PAYMENT_SESSION_SECRET、SERVER_URL=https://${{RAILWAY_PUBLIC_DOMAIN}},以及OWNER_CHAT_ID等可选变量。 - 挂载到
/data的卷,并将DB_DIR=/data,以便保存的用户数据在重启后仍然保留。 - 前往服务设置 > Deploy > Serverless,并启用 Serverless。
- 部署。启动时会自动注册 Telegram Webhook。
冷启动可能会导致首次 Telegram 或网页请求变慢;Railway 唤醒服务时,可能会在首次请求中返回 502。机器人会话和所有者回复线程保存在内存中,重启或休眠后会重置;付款/结果令牌使用 PAYMENT_SESSION_SECRET 加密封装,重启后仍可读取,直到 TTL 到期。已保存的电表 ID 会保留在 SQLite 中。
测试
# Backend tests
npm test
# Frontend tests
cd frontend && npm test
# Lint backend and frontend
npm run lint
连接你自己的 Claude(MCP)
该服务在 POST /mcp 提供公开的只读 MCP 服务,因此用户可以连接自己的 AI 助手,并自行决定其行为。无需账户或 API 密钥。
| 工具 | 功能 |
|---|---|
get_meter_balance | 当前余额(NUS 和 SUTD) |
get_meter_usage | 过去 7 天的用量和分析(仅 NUS) |
get_recent_topups | 最近最多 10 笔充值(NUS 和 SUTD) |
get_topup_link | 充值页面链接,用户可自行使用银行卡付款 |
这些工具无法付款,也不会接收银行卡信息。响应不会包含房间地址,因为响应会传递给用户的 AI 提供商。
要连接,请在 Claude 设置中将 https://<your-domain>/mcp 添加为自定义连接器。在 Claude Code 中运行:
claude mcp add --transport http evs-meter https://<your-domain>/mcp
每个人都可以在自己的助手中编写指令,例如在 Claude Project 中:
My NUS meter ID is 12345678. When I ask about aircon credit, check the balance
and work out how many days it will last from my recent usage. If it will last
under 3 days, suggest a top-up amount and give me the top-up link.
该服务是无状态的,因此可与 Railway Serverless 配合使用。每个 IP 地址每 5 分钟最多可调用工具 120 次,且无法批量发送调用。托管客户端会共享 IP 地址,因此该限制高于网站的查询限制。
工具错误以 JSON 格式返回,因此 Agent 可以决定下一步操作:
{ "code": "METER_NOT_FOUND", "message": "EVS has no NUS meter with this ID. ...", "retryable": false }
| 代码 | 可重试 | 含义 |
|---|---|---|
METER_NOT_FOUND | 否 | EVS 无法识别该系统上的电表 |
INVALID_AMOUNT | 否 | 金额超出该系统的充值限制 |
TOPUPS_PAUSED | 否 | 目前已关闭充值功能 |
EVS_UNAVAILABLE | 是 | 无法连接 EVS,或 EVS 未返回详细信息 |
RATE_LIMITED | 是 | 此 IP 地址的调用次数过多;包含 retryAfterSeconds |
get_topup_link 会先检查电表。如果无法连接 EVS,仍会返回链接,但 meterVerified 为 false。
每个工具都声明了输出架构,并返回 structuredContent;对于无法读取该字段的客户端,也会以文本形式返回相同的 JSON。
如需让 Agent 每天检查余额,并在余额不足时向你发送付款链接,请参阅定时余额检查。如需运行自己的副本,或基于此服务构建自己的自动化流程,请参阅自托管指南。
机器人命令
| 命令 | 说明 |
|---|---|
/start | 显示主菜单 |
/topup | 开始电费充值 |
/balance | 查询电表余额 |
/usage | 显示过去 7 天的用量 |
/topups | 显示最近的充值记录 |
/alerts | 显示或更改低余额提醒 |
/feedback | 提交反馈或报告问题 |
/cancel | 取消当前流程 |
/help | 显示帮助和支持的系统 |
/status | 仅限所有者查看实时服务状态 |
/stats | 仅限所有者查看运行时快照 |
低余额提醒
用户可以通过 /alerts on 选择开启提醒,该设置适用于此机器人中已保存的电表。用户在机器人中开始充值时,电表会被保存。每天会检查每个已开启提醒的电表;如果余额低于 SGD 5,机器人就会向对应的聊天发送消息。消息中有一个按钮,可以启动机器人的常规充值流程,因此用户仍可自行选择金额并使用银行卡付款。
每个电表在每次低余额期间只会收到一次提醒。检查发现余额恢复到 SGD 5 或以上后,之后再次低于此金额时可以再次提醒。查询失败时会跳过,绝不会视为余额为零。如果用户屏蔽了机器人,提醒就会停止;/forget 会连同电表一起删除提醒。
要开启每日检查:
- 使用
openssl rand -hex 32生成密钥。 - 在 Railway 上将其设置为
LOW_BALANCE_CRON_SECRET。 - 在 GitHub 上将相同的值添加为同名的仓库密钥。
随后,.github/workflows/low-balance-check.yml 会在新加坡时间 09:00 调用 POST /internal/low-balance-check;如果 Railway Serverless 已让服务进入休眠,此调用也会唤醒服务。要测试此功能,请在 GitHub Actions 选项卡中手动运行该工作流。GitHub 可能会延迟计划运行;如果仓库 60 天没有活动,GitHub 会禁用计划运行。
机器人会话流程
会话存储在内存中,TTL 为 15 分钟。同一聊天中的所有消息都会通过该聊天专属的锁按顺序处理,以防止竞态条件。在无服务器部署中,Railway 停止服务并在之后重新启动时,会话会重置。充值流程的阶段如下:
idle
→ awaiting_meter_id (8-digit ID; prefetches balance + 7-day usage;
NUS always uses cp2nus, no hostel picker)
→ awaiting_amount ($6–$50 SGD)
→ awaiting_payment (WebApp Pay button; re-prompts on text)
→ idle (reset after WebApp closes)
→ awaiting_meter_id_balance (/balance with no saved meter)
→ awaiting_meter_id_usage (/usage with no saved meter)
→ awaiting_meter_id_topups (/topups with no saved meter)
→ awaiting_feedback_rating (/feedback — star rating keyboard)
→ awaiting_feedback_text (free-text or ⏭ Skip)
→ idle
/balance、/usage 和 /topups 使用单步阶段,在收到一次回复后返回空闲状态。
支付会话
支付会话(由 /webapp/bootstrap 创建)是密封的加密令牌,TTL 为 10 分钟,与机器人会话分开。令牌包含支付所需的电表 ID、金额、地址、余额和 eNETS 网关字段。这样,即使 Railway Free 在新进程中唤醒服务,也不会丢失正在进行的支付会话。
/webapp/enets_pay 得出最终结果后,服务器会立即发送 Telegram 支付通知,并返回一个密封的结果令牌,TTL 为 24 小时。支付令牌和结果令牌都带有内部 tokenKind;支付路由会拒绝结果令牌,结果路由也会拒绝支付令牌。React 结果页面通过 GET /webapp/session?token= 读取结果数据;绝不会信任查询参数中的支付结果。CP2NUS 收据 PDF 仍保存在易失的内存缓存中,因此只有在缓存条目可用时,结果页面才会显示收据链接。
用户存储
已保存的电表 ID 和宿舍会使用 better-sqlite3 持久化到 SQLite 数据库(evs_users.db)。数据库写入 DB_DIR(如果存在,则使用 Railway Volume /data;否则使用项目根目录)。与机器人会话和支付会话不同,用户存储在重启后仍会保留。
所有者回复串接
用户提交反馈时,机器人会将通知转发到 OWNER_CHAT_ID。所有者可以直接回复这条 Telegram 通知,机器人会将回复转发给用户。回复串接通过带有 7 天 TTL 的内存 pendingReplies 映射进行跟踪。
注意:串接只会跟踪对原始通知消息的回复。如果所有者回复了自己的回复,该消息不会自动转发给用户——只有对原始转发通知的回复才会被拦截。
项目结构
├── server.js # Express entry point; serves React at /app/
├── routes/
│ ├── cp2.js # WebApp + API routes for legacy cp2 (hidden from UI)
│ ├── cp2nus.js # WebApp + API routes for cp2nus
│ ├── sutd.js # WebApp + API routes for SUTD
│ ├── mcp.js # Read-only MCP server for AI assistants
│ ├── alerts.js # Secret-protected trigger for the low-balance check
│ └── website.js # Standalone website lookup + feedback API
├── services/
│ ├── cp2Service.js # Legacy cp2 purchase flow: EVS WebPOS scraping + eNETS proxy
│ ├── cp2nusService.js # Purchase flow: EVS JSON API + eNETS PP + NETS API
│ ├── sutdService.js # SUTD balance, history, and WebPOS purchase flow
│ ├── ore.js # ORE API: meter summary, usage history, top-up history
│ ├── meterLookup.js # Balance, usage, and top-up lookups shared by the website and MCP
│ ├── paymentSession.js # Sealed payment/result tokens and receipt cache
│ ├── paymentNotification.js # Telegram payment result notification helper
│ ├── paymentSubmitLock.js # Short-lived duplicate payment submit guard
│ ├── utils.js # HTML parsing, result normalisation, XSS escaping
│ ├── validators.js # Meter ID validation and strict amount parsing
│ ├── config.js # Base URLs and shared HTTP headers
│ └── analytics.js # Event tracking and exception capture
├── bot/
│ ├── index.js # Telegraf handlers + webhook/polling runtime
│ ├── handlers/ # Command and text message handlers
│ ├── services/ # Bot session, user store, lookup helpers
│ └── constants.js # Stage names, keyboards, shared messages
├── views/
│ └── errorPage.js # Shared HTML error page for Express error responses
└── frontend/ # React + Vite frontend
├── src/
│ ├── App.jsx # React Router routes
│ ├── pages/
│ │ ├── HomePage.jsx # System selection + meter ID + amount entry
│ │ ├── LoadingPage.jsx # Spinner; calls /webapp/bootstrap
│ │ ├── CardPaymentPage.jsx # RSA card form; calls /webapp/enets_pay
│ │ ├── ResultPage.jsx # Payment outcome
│ │ └── TermsPage.jsx # Terms of Use
│ ├── components/ # Card, DetailRow, Logo, ErrorCard
│ └── lib/ # rsa.js, cardBrand.js, validation.js
└── __tests__/ # Vitest + Testing Library tests
备注
- Bot 会话存储在内存中,并会在 15 分钟后过期。支付令牌会经过密封处理;只要
PAYMENT_SESSION_SECRET保持稳定,服务重启后仍可使用;待处理的支付令牌会在 10 分钟后过期,已完成的结果令牌会在 24 小时后过期。用户存储(已保存的 meter ID)由 SQLite 提供支持,并会在服务重启后保留。 /webapp/enets_pay使用进程本地提交锁,并以商户交易参考编号为键,拒绝重复的进行中支付提交,返回 HTTP 409。- 卡片详情会在浏览器中经过 RSA 加密后再发送到服务端。服务端不会接触明文卡号或 CVV。
- cp2nus 流程会区分顶层的
netsMid(UMID_xxx)和paymtNetsMid(来自paymtSvcInfoList[0]的收单 MID)。使用错误的 MID 会导致支付失败,且不会显示错误。 - ORE(
ore.evs.com.sg)和 eNETS 初始化端点(p-1.evs.com.sg)目前会在Authorization: Bearer请求头为空的情况下调用,并且能够成功——不需要令牌。EVS2 门户还运行着用户身份验证服务(evs2u.evs.com.sg/login),可签发有效期较短(约 1 小时)的 JWT,因此 EVS 未来可能会开始要求这些端点使用真实令牌。如果查询或充值开始返回401/403,这很可能就是原因;届时需要在流程中获取并附加 JWT。 - 可接受的充值金额输入格式严格:纯数字以及常见货币前缀,例如
$10或S$ 10.00;格式错误的混合字符串会被拒绝。NUS 的最低充值金额为 $6.00 SGD,SUTD 的最低充值金额为 $10.00 SGD,目前所有系统的充值上限均为 $50.00 SGD。 - 网站入口(
/app/)和 Telegram Mini App 使用相同的 Express 路由和 React 页面,没有单独的代码路径。 - 可通过
/maintenanceon(所有者命令)在运行时启用全局维护模式,也可在启动时设置MAINTENANCE_MODE=true启用。用户会在应用和 Bot 中看到全局维护消息;/health和 Telegram webhook 仍保持可用,以确保服务可以访问,且所有者可以运行/maintenanceoff。设置MAINTENANCE_MESSAGE可自定义提示文本。GLOBAL_MAINTENANCE=true也可作为旧版别名使用。 - 可通过
/topupoff(所有者命令)在运行时禁用充值,也可在启动时设置TOPUP_DISABLED=true禁用。如果 Bot 用户在设置该标志时正处于充值会话中,其会话将被重置,并显示维护消息。禁用充值期间,网站的支付开始、加载、卡片、bootstrap 和卡片提交路由也会返回维护响应;查询、条款、结果和反馈路由仍可使用。
许可协议
MIT。这是一个非官方工具,与 NUS、SUTD、EVS 或 eNETS 均无关联。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。