Skip to content
Novaix 目前处于早期开发阶段,功能尚未稳定,可能存在严重的 Bug。请勿用于生产环境。

插件系统

Novaix 支持通过 JavaScript 插件扩展系统功能。您可以编写插件来对接自定义的支付渠道、人机验证服务、身份认证平台、短信服务、邮件服务和通知渠道,无需修改系统源代码。

插件类型

类型说明需要导出的函数示例场景
payment支付渠道createPaymenthandleCallbackgetMethodsUSDT、虚拟货币支付等
captcha人机验证verify极验、reCAPTCHA、hCaptcha、Turnstile 等
oauth社会化登录authURLexchangegetMethods第三方聚合登录平台
kyc身份认证verify(必须),initFace + getFaceResult(可选)第三方人脸识别、实名核验
sms短信服务send自定义短信平台
mail邮件服务send自定义邮件发送 API
notify通知渠道send自定义推送服务

插件生命周期

安装插件

有三种方式安装插件:

插件市场安装

在后台「插件管理」页面切换到「插件市场」Tab,浏览可用的官方插件,点击「安装」即可自动下载并加载。已安装的插件如有新版本,会显示「更新」按钮。

上传安装

在后台「插件管理」页面点击「上传安装」按钮,选择 .zip 格式的插件包上传。系统会自动解压、校验并加载插件。

手动安装

将插件目录放到 Novaix 数据目录下的 data/plugins/ 中:

data/plugins/
└── my-payment/            ← 插件目录(目录名任意)
    ├── manifest.json      ← 插件信息和配置字段定义
    ├── main.js            ← 插件逻辑代码(后端,所有类型必需)
    └── widget.js          ← 前端渲染脚本(仅 captcha 类型必需)

手动放置后,在插件管理页点击「刷新」或重启 Novaix 即可加载。


插件加载成功后,会自动出现在后台侧边栏「插件」管理页面中。在插件列表中点击齿轮图标即可配置该插件的参数。新安装的插件默认禁用,需要管理员手动启用。

不同类型插件的启用方式

  • 支付 / 通知 / 社会化登录 类型:在插件管理页启用开关即可,系统会自动将其加入可用渠道列表
  • 短信 / 邮件 / 认证 / 人机验证 类型:除了在插件管理页启用外,还需要到对应的设置页面(如「短信」设置、「人机验证」设置)将当前渠道切换为该插件

重载与卸载

重载插件

修改插件的 main.jsmanifest.json 后,在插件管理页点击重载按钮(🔄 图标)即可热重载,无需重启服务。系统会重新读取磁盘上的文件并重新注册插件。

卸载插件

在插件管理页点击卸载按钮,确认后系统会:

  • 从渠道注册表中移除该插件
  • 清理数据库中的所有配置数据
  • 删除磁盘上的插件目录

WARNING

官方内置插件(如易支付)不允许卸载。卸载操作不可撤销,请确认不再需要该插件后再操作。

导出插件

在插件管理页点击导出按钮,即可将插件打包为 .zip 文件下载。导出的 zip 包含 manifest.jsonmain.js(不含配置数据),可直接通过「上传安装」导入到其他 Novaix 实例。

插件市场配置

插件市场默认从 Novaix 官方索引获取可用插件列表。如需使用自定义的插件源,可在配置文件中设置:

yaml
plugin:
  dir: data/plugins
  marketplace_url: https://your-server.com/plugins/index.json

TIP

插件目录的路径可在配置文件中通过 plugin.dir 自定义,默认为 data/plugins。也可通过环境变量 NOVAIX_PLUGIN_DIR 覆盖。

官方内置插件

Novaix 自带以下官方插件,会随版本升级自动更新:

  • 易支付(epay)— 支付渠道,兼容彩虹易支付 / ZPAY 等通用规范
  • 彩虹聚合登录(clogin)— 社会化登录,支持 QQ、微信、支付宝等
  • 短信宝(smsbao)— 短信服务,低价短信验证码
  • PushPlus(pushplus)— 通知渠道,通过微信公众号推送通知

如果您需要自定义官方插件的行为,请复制为新的插件 ID(如 my-epay),不要直接修改 data/plugins/ 下的官方插件文件,否则升级时会被覆盖。

manifest.json

每个插件必须包含一个 manifest.json 文件,用于描述插件信息和配置字段。

json
{
  "id": "my-face-verify",
  "name": "人脸认证",
  "version": "1.0.0",
  "description": "对接 xxx 平台的人脸识别二要素核验",
  "author": {
    "name": "作者名",
    "email": "[email protected]",
    "url": "https://example.com"
  },
  "license": "MIT",
  "homepage": "https://github.com/example/novaix-plugin-face",
  "type": "kyc",
  "requires": ">=0.6.0",
  "config": [
    { "key": "api_key", "label": "API Key", "type": "password", "required": true },
    { "key": "api_url", "label": "接口地址", "type": "text", "default": "https://api.example.com" }
  ]
}

清单字段

字段必填说明
id插件唯一标识,仅允许小写字母、数字和连字符,2-63 字符(如 my-face-verify)。不能与内置渠道名称冲突
name插件展示名称
version语义化版本号
description插件简短描述
author作者信息,包含 nameemailurl
type插件类型:paymentcaptchaoauthkycsmsmailnotify
requiresNovaix 版本兼容约束(如 >=0.6.0),不满足时插件不会加载。兼容旧字段名 novaix
config配置字段数组,定义管理员需要填写的配置项
frontend前端元数据(仅 captcha 类型使用),见人机验证插件

配置字段

config 数组中的每个对象支持以下属性:

属性类型说明
keystring字段标识(必填)
labelstring展示标签(必填)
typestring字段类型,见下表(必填)
requiredboolean是否必填,保存时校验
sensitiveboolean是否敏感,敏感字段会加密存储(password 类型默认敏感)
defaultstring / array / object默认值。multiselect 类型可直接写数组如 ["a","b"]keyvalue 类型可直接写对象如 {"k":"v"},也兼容 JSON 字符串形式
optionsarrayselect/multiselect/radio 类型的选项列表
options_fromobject动态选项,依赖另一个字段的值切换选项列表,见动态选项
helpstring字段下方的帮助说明文字
placeholderstring输入占位提示文字
spannumber表单列宽:1 占半行,2 或不设置占整行
groupstring字段分组标题,相同 group 值的字段归为一组显示
group_collapsedboolean该分组默认折叠(仅需在分组首个字段上标记)
whenobject条件显示,见条件显示
readonlyboolean只读字段,不可编辑,保存时跳过
copyableboolean显示复制按钮,方便复制字段值
computedstring值模板,前端自动解析变量。支持 (站点 URL)和 (插件 ID)

字段类型详解

type渲染控件说明存储格式
text单行文本输入框通用文本输入,如 API 地址、应用 ID原始字符串
password密码输入框(圆点遮罩)密钥、Secret 等敏感值,自动加密存储加密字符串
textarea多行文本域(4 行)证书内容、私钥等长文本。设置 sensitive: true 时显示遮罩并加密存储原始字符串(sensitive 时加密)
select下拉单选框需配合 optionsoptions_from选中项的 value
multiselect复选框组多选,需配合 optionsoptions_fromJSON 数组字符串,如 ["a","b"]
radio单选按钮组选项较少(2-4 个)时比 select 更直观选中项的 value
number数字输入框仅接受数字输入数字字符串
bool开关(Switch)适合启用/禁用类设置"true""false"
urlURL 输入框自动校验 URL 格式原始字符串
code等宽字体多行编辑区(8 行)代码片段、模板等原始字符串
keyvalue可增删的键值对列表自定义 HTTP header、额外参数等JSON 对象字符串,如 {"key":"value"}
divider分隔线 + 说明文字纯视觉元素,不产生配置值。用 labelhelp 显示标题和说明

示例:使用各种字段类型

json
{
  "config": [
    { "key": "api_url", "label": "API 地址", "type": "text", "required": true },
    { "key": "api_key", "label": "API 密钥", "type": "password", "required": true },
    { "key": "private_key", "label": "私钥", "type": "textarea", "sensitive": true },
    { "key": "region", "label": "区域", "type": "select", "default": "cn", "options": [
      { "value": "cn", "label": "中国大陆" },
      { "value": "hk", "label": "中国香港" },
      { "value": "us", "label": "美国" }
    ]},
    { "key": "timeout", "label": "超时时间(秒)", "type": "number", "default": "30" },
    { "key": "debug", "label": "调试模式", "type": "bool", "default": "false" }
  ]
}

条件显示(when

当某个字段只在特定条件下才需要显示时,使用 when 属性。隐藏的字段不参与验证和保存。

单条件模式:

json
{ "key": "client_ip", "label": "客户端 IP", "type": "text", "when": { "key": "mode", "value": "mapi" } }

支持的操作符(op 字段):

op说明示例
eq(默认)等于{"key": "mode", "value": "mapi"}
neq不等于{"key": "mode", "op": "neq", "value": "jump"}
in值在列表中{"key": "mode", "op": "in", "values": ["mapi", "api"]}
empty值为空{"key": "token", "op": "empty"}
notEmpty值非空{"key": "token", "op": "notEmpty"}
contains包含子串{"key": "url", "op": "contains", "value": "https"}

多条件组合:

使用 conditions 数组 + logic"and""or",默认 "and")组合多个条件:

json
{
  "key": "mapi_timeout",
  "label": "MAPI 超时",
  "type": "number",
  "when": {
    "logic": "and",
    "conditions": [
      { "key": "mode", "value": "mapi" },
      { "key": "advanced", "value": "true" }
    ]
  }
}

when.key 必须引用同一 config 数组中已定义的字段 key,引用不存在的 key 会导致插件加载失败。条件嵌套最多一层。

动态选项(options_from

select/multiselect/radio 的选项需要根据另一个字段的值动态变化时,使用 options_from

json
{
  "key": "sub_type",
  "label": "子类型",
  "type": "select",
  "options_from": {
    "key": "type",
    "map": {
      "alipay": [
        { "value": "h5", "label": "H5" },
        { "value": "pc", "label": "PC 网页" }
      ],
      "wxpay": [
        { "value": "native", "label": "Native 扫码" },
        { "value": "jsapi", "label": "JSAPI" }
      ]
    }
  }
}

当依赖字段 type 的值为 alipay 时显示 H5/PC 选项,值为 wxpay 时显示 Native/JSAPI 选项。options_from 可与 options 共存,options 作为依赖值不在 map 中时的后备选项。当依赖字段值变化后,如果当前选中值不在新选项列表中,会自动清空。

示例:只读可复制字段(回调地址)

json
{
  "key": "callback_url",
  "label": "回调地址",
  "type": "text",
  "readonly": true,
  "computed": "{{baseURL}}/api/callbacks/{{pluginID}}",
  "copyable": true,
  "help": "请将此地址填写到支付平台的异步通知 URL 中"
}

computed 模板中的 会替换为系统设置的站点 URL, 替换为当前插件 ID。配合 readonlycopyable,管理员可以直接复制该地址,无需手动拼接。

示例:分组折叠

json
[
  { "key": "api_url", "label": "API 地址", "type": "text", "required": true },
  {
    "key": "advanced_divider", "label": "高级配置", "type": "divider",
    "help": "以下为可选的高级配置项",
    "group": "高级设置", "group_collapsed": true
  },
  { "key": "timeout", "label": "超时时间", "type": "number", "default": "30", "group": "高级设置" },
  { "key": "extra_params", "label": "额外参数", "type": "keyvalue", "group": "高级设置" }
]

group_collapsed: true 标记在分组首个字段上,该分组默认折叠,管理员可点击展开查看。

TIP

paymentnotifyoauth 类型的插件会自动注入 enabled 启用开关字段,无需在 config 中手动声明。captcha 类型的插件还需要提供 widget.js 前端渲染脚本,详见人机验证插件

宿主 API

系统向 JS 运行时注入了以下全局对象,可在插件中直接调用。

http

方法签名说明
http.request(method, url, opts?) → Response发送 HTTP 请求

参数 opts

属性类型说明
headers{key: value}请求头
bodystring请求体
timeoutnumber超时秒数,默认 30,上限 60

返回值 Response

属性类型说明
statusnumberHTTP 状态码
headers{key: value}响应头(key 全小写)
bodystring响应体(最大 1MB)
js
var resp = http.request("POST", "https://api.example.com/verify", {
    headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + config.api_key
    },
    body: JSON.stringify({ name: "张三" }),
    timeout: 15
});
var data = JSON.parse(resp.body);

安全限制

HTTP 请求内置 SSRF 防护:禁止访问内网地址(127.0.0.0/810.0.0.0/8172.16.0.0/12192.168.0.0/16169.254.0.0/16),包括通过 DNS 解析到内网 IP 的域名。重定向最多 3 次,响应体最大 1MB。

crypto

所有函数返回十六进制小写字符串。

方法签名说明
crypto.md5(str) → stringMD5 哈希
crypto.sha1(str) → stringSHA-1 哈希
crypto.sha256(str) → stringSHA-256 哈希
crypto.hmacMD5(key, data) → stringHMAC-MD5 签名
crypto.hmacSHA256(key, data) → stringHMAC-SHA256 签名
js
var sign = crypto.hmacSHA256(config.secret_key, "data-to-sign");
var hash = crypto.md5("hello");

base64

方法签名说明
base64.encode(str) → stringBase64 编码
base64.decode(str) → stringBase64 解码

url

方法签名说明
url.encode(str) → stringURL 编码(encodeURIComponent 等效)
url.buildQuery(params) → string将对象转为查询字符串,按 key 排序
js
var qs = url.buildQuery({ b: "2", a: "1" }); // "a=1&b=2"

log

日志会输出到系统日志,自动附加 plugin_id 字段。

方法签名说明
log.info(msg)信息级别日志
log.warn(msg)警告级别日志
log.error(msg)错误级别日志

config

只读对象,包含管理员在插件配置中填写的值。属性名对应 manifest.jsonconfig 数组的 key

js
var apiKey = config.api_key;
var apiUrl = config.api_url;

JS 内置对象

以下标准 JavaScript 对象可直接使用:

  • JSON.parse() / JSON.stringify() — JSON 序列化
  • parseInt() / parseFloat() / Math.* — 数值处理
  • encodeURIComponent() / decodeURIComponent() — URI 编码
  • String.prototype.* — 字符串操作(splitreplacetrimindexOf 等)
  • Array.prototype.* — 数组操作(pushmapfiltersortjoin 等)
  • Date — 日期对象(new Date()Date.now() 等)

插件函数接口

支付插件 (payment)

createPayment(req) 必须导出

入参 req

字段类型说明
orderNostring系统订单号
amountnumber金额,单位
currencystring货币代码(如 CNYUSD
subjectstring订单标题
notifyURLstring异步回调地址
returnURLstring支付完成后的跳转地址
clientIPstring用户 IP
methodstring用户选择的子支付方式标识(可为空,聚合支付时由 getMethods 暴露)

返回值:

字段类型说明
payURLstring支付跳转 URL 或二维码链接
tradeNostring渠道方交易号(可为空)
qrCodebooleantrue 表示 payURL 是二维码内容而非跳转链接

handleCallback(req) 必须导出

入参 req

字段类型说明
methodstringHTTP 方法(GETPOST
urlstring完整请求 URL
headersobject请求头 {key: value}
bodystring请求体原始字符串
queryobjectURL 查询参数 {key: value}

返回值:

字段类型说明
orderNostring系统订单号
tradeNostring渠道方交易号
amountnumber金额,单位
currencystring货币代码
successboolean是否支付成功
rawDatastring原始回调数据(用于日志记录)

TIP

回调地址格式为 站点地址/api/callbacks/插件ID。例如插件 ID 为 my-pay,则回调地址为 https://your-domain.com/api/callbacks/my-pay

系统会自动进行金额校验和幂等处理,插件只需返回正确的回调结果。

callbackOKResponse() 可选导出

自定义回调成功时返回给支付平台的响应(默认返回 200 text/plain "success")。支持两种返回格式:

js
// 简写:只自定义 body
function callbackOKResponse() { return "OK"; }

// 完整形式:自定义状态码、Content-Type 和 body
function callbackOKResponse() {
    return { status: 200, contentType: "application/json", body: '{"code":"SUCCESS"}' };
}

callbackFailResponse() 可选导出

自定义回调失败时返回给支付平台的响应(默认返回 400 text/plain "fail")。格式同上。

getMethods() 可选导出

聚合支付插件可以导出此函数,暴露多种子支付方式供用户选择。返回数组时,用户在充值/下单页面会看到展开后的具体支付方式(如"支付宝"、"微信支付"),而非笼统的渠道名称。

js
function getMethods() {
    var types = config.type.split(",");
    if (types.length <= 1) return null; // 单选时不展开
    
    var labels = { "alipay": "支付宝", "wxpay": "微信支付" };
    var methods = [];
    for (var i = 0; i < types.length; i++) {
        methods.push({ name: types[i], label: labels[types[i]] || types[i] });
    }
    return methods;
}

返回值(数组或 null):

字段类型说明
namestring子方式标识(会传入 createPaymentreq.method
labelstring展示名称

返回 null 或空数组时,该渠道整体作为一个选项展示,行为与未导出此函数相同。

社会化登录插件 (oauth)

authURL(req) 必须导出

入参 req

字段类型说明
statestringCSRF 防护状态码,必须嵌入到授权 URL 中
redirectURLstring系统回调地址
methodstring用户选择的子登录方式标识(可为空,聚合登录时由 getMethods 暴露)

返回值: string — 用户授权跳转 URL。

WARNING

如果第三方平台不支持标准 OAuth state 参数透传(如聚合登录平台),需要将 state 编码到 redirectURL 的查询参数中,确保回调时 state 能被系统正确接收。

exchange(req) 必须导出

入参 req

字段类型说明
codestring授权码
redirectURLstring系统回调地址
methodstring用户选择的子登录方式标识(可为空,与 authURL 中传入的一致)

返回值:

字段类型说明
providerUserIDstring提供商内唯一用户标识
emailstring用户邮箱(可为空)
emailVerifiedboolean邮箱是否已验证
displayNamestring用户昵称
avatarURLstring用户头像 URL

getMethods() 可选导出

聚合登录插件可以导出此函数,暴露多种子登录方式。返回数组时,登录页会为每种方式分别显示一个按钮(如"QQ"、"微信"),而非一个笼统的渠道按钮。用法和返回值格式与支付插件的 getMethods 相同。

用户点击某个登录按钮后,选中的方式标识会通过 req.method 传入 authURLexchange,插件据此调用对应的第三方 API。

人机验证插件 (captcha)

人机验证插件与其他类型不同,它需要前端渲染(加载第三方 SDK、显示验证码组件)和后端校验(验证用户提交的 token)两部分配合工作。因此 captcha 插件除了 manifest.jsonmain.js,还需要一个在浏览器中执行的 widget.js 文件。

目录结构

my-captcha/
├── manifest.json      ← 插件信息 + frontend.csp_domains
├── main.js            ← 后端校验脚本(Goja 执行)
└── widget.js          ← 前端渲染脚本(浏览器执行)

manifest.json 额外字段

captcha 类型的 manifest 支持 frontend 字段,用于声明前端 SDK 的域名白名单(系统会自动加入 CSP 策略):

json
{
  "id": "turnstile",
  "name": "Cloudflare Turnstile",
  "version": "1.0.0",
  "type": "captcha",
  "author": { "name": "Novaix" },
  "frontend": {
    "csp_domains": ["https://challenges.cloudflare.com"]
  },
  "config": [
    { "key": "site_key", "label": "Site Key", "type": "text", "required": true },
    { "key": "secret_key", "label": "Secret Key", "type": "password", "required": true }
  ]
}

csp_domains 数组中的域名会被自动添加到页面的 script-srcconnect-srcframe-src CSP 指令中,确保第三方验证码 SDK 不被浏览器拦截。

verify(req) 必须导出

main.js 中导出 verify 函数,用于服务端校验验证码 token。

入参 req

字段类型说明
tokenstring前端验证通过后获得的 token
ipstring用户 IP 地址

返回值:

字段类型说明
successboolean校验是否通过
errorstring失败原因(可选,successfalse 时使用)

示例 main.js(Turnstile):

js
function verify(req) {
    var resp = http.request("POST", "https://challenges.cloudflare.com/turnstile/v0/siteverify", {
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: url.buildQuery({
            secret: config.secret_key,
            response: req.token,
            remoteip: req.ip
        })
    });
    var data = JSON.parse(resp.body);
    return { success: data.success === true, error: data.success ? "" : "验证失败" };
}

widget.js 契约

widget.js 在用户浏览器中执行,必须定义全局函数 window.__novaix_captcha_init

js
/**
 * @param {HTMLDivElement} container - 挂载点 DOM 元素
 * @param {Object} config - 插件的公开配置值(非敏感字段,如 site_key)
 * @param {Object} callbacks
 * @param {Function} callbacks.onSuccess(token) - 验证通过时调用,传入 token
 * @param {Function} callbacks.onError(msg) - 验证失败时调用
 * @param {Function} callbacks.onExpired() - 验证过期时调用
 */
window.__novaix_captcha_init = function(container, config, callbacks) {
    // 在这里加载第三方 SDK 并渲染验证码组件
};

// 可选:清理函数,组件卸载时调用
window.__novaix_captcha_destroy = function() {
    // 清理 SDK 实例和事件监听
};

示例 widget.js(Turnstile):

js
window.__novaix_captcha_init = function(container, config, callbacks) {
    var script = document.createElement('script');
    script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=__cfReady&render=explicit';
    window.__cfReady = function() {
        window.__novaix_captcha_widget_id = turnstile.render(container, {
            sitekey: config.site_key,
            callback: callbacks.onSuccess,
            'error-callback': callbacks.onError,
            'expired-callback': callbacks.onExpired,
        });
    };
    document.head.appendChild(script);
};

window.__novaix_captcha_destroy = function() {
    if (window.__novaix_captcha_widget_id) {
        turnstile.remove(window.__novaix_captcha_widget_id);
    }
};

管理员配置流程

  1. 安装 captcha 插件(上传或从市场安装)
  2. 在插件管理页启用插件并配置 API Key
  3. 进入「设置 → 安全与用户 → 人机验证」,启用人机验证并选择渠道
  4. 在「保护范围」中勾选需要显示验证码的表单(登录、注册、发送验证码、重置密码)

身份认证插件 (kyc)

KYC 插件支持两种模式:二要素验证(verify)和人脸识别(initFace + getFaceResult)。verify 为必须导出的函数,人脸识别相关函数为可选——如果同时导出了 initFacegetFaceResult,系统会自动识别该插件支持人脸识别模式。

verify(req) 必须导出

入参 req

字段类型说明
realNamestring真实姓名
idNumberstring身份证号

返回值:

字段类型说明
matchboolean姓名与身份证号是否匹配
rawstring第三方原始响应(用于日志记录)

initFace(req) 可选导出

发起人脸识别认证,返回跳转 URL。需与 getFaceResult 同时导出。

入参 req

字段类型说明
realNamestring真实姓名
idNumberstring身份证号
redirectUrlstring完成后回跳 URL
userIdnumber用户 ID

返回值:

字段类型说明
tokenstring会话令牌,用于后续查询结果
urlstring用户需要跳转的 H5 页面地址
bizIdstring业务标识(部分渠道查询结果时需要)

getFaceResult(req) 可选导出

查询人脸识别认证结果。需与 initFace 同时导出。

入参 req

字段类型说明
tokenstringinitFace 返回的会话令牌
bizIdstringinitFace 返回的业务标识

返回值:

字段类型说明
passedboolean人脸识别是否通过
rawstring第三方原始响应(用于日志记录)

短信插件 (sms)

send(phone, params) 必须导出

参数类型说明
phonestring手机号
paramsobject模板变量,如 {code: "1234"}

无返回值。发送失败请 throw new Error("错误信息")

邮件插件 (mail)

send(to, subject, htmlBody) 必须导出

参数类型说明
tostring收件人地址
subjectstring邮件主题
htmlBodystringHTML 格式的邮件内容

无返回值。发送失败请 throw new Error("错误信息")

通知插件 (notify)

send(title, body) 必须导出

参数类型说明
titlestring通知标题
bodystring通知正文

无返回值。发送失败请 throw new Error("错误信息")

完整示例

以下是一个支持二要素验证和人脸识别的 KYC 插件示例:

manifest.json

json
{
  "id": "face-example",
  "name": "示例身份认证",
  "version": "1.0.0",
  "description": "对接示例身份认证平台,支持二要素验证和人脸识别",
  "author": { "name": "示例作者" },
  "type": "kyc",
  "config": [
    { "key": "api_key", "label": "API Key", "type": "password", "required": true },
    { "key": "api_url", "label": "接口地址", "type": "text", "required": true, "default": "https://api.example.com" }
  ]
}

main.js

js
// 二要素验证(必须导出)
function verify(req) {
    var sign = crypto.hmacSHA256(config.api_key, req.realName + req.idNumber);

    var resp = http.request("POST", config.api_url + "/identity/verify", {
        headers: {
            "Content-Type": "application/json",
            "X-Signature": sign
        },
        body: JSON.stringify({
            real_name: req.realName,
            id_number: req.idNumber
        })
    });

    var data = JSON.parse(resp.body);

    if (resp.status !== 200) {
        throw new Error("认证接口异常: " + data.message);
    }

    return {
        match: data.verified === true,
        raw: resp.body
    };
}

// 人脸识别 - 发起(可选导出,需与 getFaceResult 同时导出)
function initFace(req) {
    var resp = http.request("POST", config.api_url + "/face/init", {
        headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + config.api_key
        },
        body: JSON.stringify({
            name: req.realName,
            id_card: req.idNumber,
            callback_url: req.redirectUrl
        })
    });

    var data = JSON.parse(resp.body);

    if (resp.status !== 200) {
        throw new Error("人脸识别初始化失败: " + data.message);
    }

    return {
        token: data.biz_token,
        url: data.verify_url,
        bizId: data.biz_id || ""
    };
}

// 人脸识别 - 查询结果(可选导出,需与 initFace 同时导出)
function getFaceResult(req) {
    var resp = http.request("POST", config.api_url + "/face/result", {
        headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + config.api_key
        },
        body: JSON.stringify({
            token: req.token,
            biz_id: req.bizId
        })
    });

    var data = JSON.parse(resp.body);

    if (resp.status !== 200) {
        throw new Error("查询人脸识别结果失败: " + data.message);
    }

    return {
        passed: data.result === "success",
        raw: resp.body
    };
}

调试与排查

  • 插件加载状态可在后台「插件」管理页面查看,加载失败会显示错误原因
  • 插件中的 log.info/warn/error 会输出到系统日志,带有 plugin_id 字段便于过滤
  • 如果 JS 脚本中抛出异常(throw new Error(...)),系统会将其转为对应操作的错误返回
  • 插件配置修改后不需要重启,系统会在下次调用时使用最新配置

注意事项

  • 插件使用 ECMAScript 5.1 语法,支持部分 ES6 特性(如箭头函数、模板字符串、Proxy 等),但不支持 async/awaitimport/export
  • 脚本顶层只应定义函数和常量,不要在顶层执行 HTTP 请求或业务逻辑(启动校验阶段会执行顶层代码,此时宿主 API 为 stub 实现)。配置值应在函数内部通过 config.* 读取
  • passwordsensitive 字段会自动加密存储,在 JS 中读取时已是明文
  • 金额在系统内部统一使用为单位(int64),插件中需要注意与第三方平台的金额单位转换
  • 新安装的插件默认禁用,需要管理员在插件管理页面手动启用
  • 启动时系统会预编译脚本并检查必需函数是否存在,缺失会在插件管理页面显示错误
  • 所有插件函数调用均有 30 秒超时保护(适配器层自动添加),超时会自动中断执行
  • HTTP 请求的 timeout 参数上限为 60 秒
  • 插件 ID 不能与内置渠道名称冲突(如 alipaywechataliyuntencent 等)
  • 禁用支付/通知类型插件后,对应渠道会立即从可用列表中移除