微昕·砚文档
Plugins

支付插件运行时

支付渠道通过 .yan-plugin 包接入。宿主只负责订单、幂等、配置加密、回调收件箱、积分入账和对账;插件负责渠道协议转换。

包结构

my-payment.yan-plugin
├── manifest.json
├── backend/provider                  # 规范副本:linux/amd64
├── backend/provider-linux-amd64
├── backend/provider-linux-arm64
├── backend/provider-darwin-amd64
├── backend/provider-darwin-arm64
├── backend/provider-windows-amd64.exe
├── backend/provider-windows-arm64.exe
├── assets/icon.svg
└── docs/README.md

Manifest 的支付贡献声明 runtime.backend 为 rpc 或 wasm,并通过 runtime.backendEntry 指定后端入口。当前版本首先启用 rpc:宿主以一次 JSON 请求启动入口进程,进程返回一次 JSON 响应。WASM 使用相同的 yan.payment/v1 数据契约,运行时启用后即可复用插件实现。

backendEntry 仍写 backend/provider。宿主按当前 GOOS/GOARCH 依次尝试:

  1. backend/provider-{goos}-{goarch}[.exe]
  2. backend/{goos}-{goarch}/provider[.exe]
  3. backend/provider[.exe],再回退到无扩展名的 backend/provider

选中文件后会校验 ELF / Mach-O / PE 魔数和 CPU,不兼容的文件会被跳过,不会拿 Linux ELF 在 macOS 或 Windows 上硬启动。

标准操作

入口进程接收以下操作名:

操作返回值
validate_config空数据;失败返回 ok:false
create_orderCheckout:mode、value、expiresAt
query_order标准 Result
close_order标准 Result
verify_notificationNotification,请求体通过 bodyBase64 传入
download_trade_billBillRecord[]

create_order 的 Checkout.value 随 mode 含义不同:qr_code 可以是待编码载荷(如微信支付 weixin://...),也可以是渠道已经画好的二维码图片 URL(如虎皮椒 url_qrcode,形如 https://api.xunhupay.com/qrcode.png)。钱包识别图片 URL 后直接展示,不会把图片地址再编码成新的二维码。redirect 由宿主包装成 /api/payments/orders/:id/checkout 跳转。扫码区固定白底深色码点,避免暗色主题反色后难扫。

请求固定带有 version: "yan.payment/v1"。插件不得访问宿主数据库,不得自行入账积分。宿主目前控制进程工作目录、环境变量、标准输出大小和 30 秒超时;支付宝、微信实现还会在插件内部校验网关和账单下载域名。通用插件的操作系统级沙箱、签名校验和网络白名单尚未启用,不应直接运行不受信任的支付插件。

原生包的平台边界

runtime.backend: "rpc" 的入口是原生可执行文件,不是平台无关静态资源。仓库中的官方支付包同时包含 macOS、Linux、Windows 的 amd64/arm64 产物;backend/provider 固定为 linux/amd64 副本,供 Linux CI 和默认冒烟使用。

官方 backend 镜像在 Docker 构建中读取 TARGETOS 和 TARGETARCH,设置 PAYMENT_PLUGIN_GOOS / PAYMENT_PLUGIN_GOARCH,只编译镜像目标平台并重新打包 .yan-plugin,避免把 darwin/windows 二进制打进 Linux 镜像。目录文件与包内文件会比较内容,并在目标平台容器中真实执行冒烟请求。

在 POSIX 环境重建官方支付包:

PAYMENT_PLUGIN_CGO_ENABLED=0 \
  ./plugin-packages/build-packages.sh --payments-only

不设置 PAYMENT_PLUGIN_GOOS 时会交叉编译全部六个目标。只构建当前镜像平台时:

PAYMENT_PLUGIN_GOOS=linux \
PAYMENT_PLUGIN_GOARCH=amd64 \
PAYMENT_PLUGIN_CGO_ENABLED=0 \
  ./plugin-packages/build-packages.sh --payments-only

第三方 RPC 插件至少需要提供当前宿主对应的 tagged 文件;只放 Linux ELF 时,macOS / Windows 会在启动前报格式不兼容,而不是当作脚本解释器缺失。

运行时缓存与诊断

宿主以完整 .yan-plugin 字节的 SHA-256 作为 package 文件名和 runtime/<digest> 缓存键。provider 内容变化会产生新 digest 和新运行目录;解包先写临时目录,完成后写入 ready marker 并原子发布,旧目录不会覆盖新包,也不要求升级时手工清空整个数据卷。ready 检查接受任意平台的 RPC 产物,不要求 backend/provider 这个规范文件名一定存在。

执行前,Linux 会拒绝 Mach-O、Windows PE、损坏的 ELF 和与当前 CPU 不匹配的 ELF;macOS 会拒绝 ELF/PE 以及 CPU 不匹配的 Mach-O;Windows 会拒绝 ELF/Mach-O 以及 CPU 不匹配的 PE。管理员可区分可执行文件缺失或加载器不可用、权限不足、格式或架构不兼容、进程异常退出、超时和无效 JSON。服务端启动日志只记录插件 ID、provider ID、宿主平台、可执行路径和操作系统错误;不会记录 RPC 请求、支付配置、密钥或插件 stderr。

Manifest 示例

{
  "apiVersion": "yan.plugin/v1",
  "id": "example-aggregate-payment",
  "name": "Example 聚合支付",
  "version": "1.0.0",
  "author": "Example",
  "enabled": true,
  "runtime": { "backend": "rpc", "backendEntry": "backend/provider" },
  "configuration": {
    "fields": [
      { "name": "merchantId", "type": "string", "required": true },
      { "name": "secret", "type": "password", "required": true, "secret": true }
    ]
  },
  "contributes": {
    "paymentProviders": [{
      "id": "example-qr",
      "label": "Example 聚合支付",
      "icon": "assets/icon.svg",
      "checkoutMode": "qr_code",
      "identityFields": ["merchantId"],
      "expiryPolicy": { "defaultMinutes": 30, "minMinutes": 5, "maxMinutes": 1440 },
      "notificationSuccess": { "status": 204 },
      "notificationFailure": { "status": 400 }
    }]
  }
}

历史订单绑定插件版本和包哈希。升级不会替换未完结订单所需的运行文件;宿主检测到未完结订单时会拒绝支付插件升级或卸载,直到订单进入终态。签名根、WASM 执行器和通用网络白名单将在后续安全阶段加入。

On this page