feat(payment): 虎皮椒聚合支付 - 官方插件与跨平台可执行文件

新增 xunhupay 官方包,支付 RPC 与宿主只保留订单和验签边界;官方支付包同时带上各系统 provider,避免把 Linux ELF 当成本机入口。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-17 23:32:11 +08:00
parent 656f64a3ce
commit bb2a79d365
63 changed files with 2070 additions and 128 deletions

View File

@@ -3,8 +3,9 @@ set -eu
root_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo_dir=$(CDPATH= cd -- "$root_dir/.." && pwd)
payment_plugins="official-payment-wechat-native official-payment-alipay-page"
payment_plugins="official-payment-wechat-native official-payment-alipay-page official-payment-xunhupay"
payments_only=false
default_payment_targets="linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64"
if [ "${1:-}" = "--payments-only" ]; then
payments_only=true
@@ -21,10 +22,9 @@ else
node "$root_dir/embed-documentation.mjs"
fi
# Payment backends are executable plugin payloads. They are compiled outside
# the host binary and then included under backend/provider in the package.
payment_goos=${PAYMENT_PLUGIN_GOOS:-$(go env GOOS)}
payment_goarch=${PAYMENT_PLUGIN_GOARCH:-$(go env GOARCH)}
# Payment backends are executable plugin payloads. Unset PAYMENT_PLUGIN_GOOS
# builds every supported host platform into the package. Docker image builds
# set PAYMENT_PLUGIN_GOOS/GOARCH to the image TARGETOS/TARGETARCH only.
payment_cgo=${PAYMENT_PLUGIN_CGO_ENABLED:-0}
case "$payment_cgo" in
@@ -35,32 +35,90 @@ case "$payment_cgo" in
;;
esac
if [ -n "${PAYMENT_PLUGIN_GOOS:-}" ]; then
if [ -z "${PAYMENT_PLUGIN_GOARCH:-}" ]; then
echo "PAYMENT_PLUGIN_GOARCH is required when PAYMENT_PLUGIN_GOOS is set" >&2
exit 2
fi
payment_targets="${PAYMENT_PLUGIN_GOOS}/${PAYMENT_PLUGIN_GOARCH}"
canonical_target="$payment_targets"
else
payment_targets="$default_payment_targets"
canonical_target="linux/amd64"
fi
payment_artifact_name() {
goos=$1
goarch=$2
if [ "$goos" = "windows" ]; then
printf 'provider-%s-%s.exe\n' "$goos" "$goarch"
else
printf 'provider-%s-%s\n' "$goos" "$goarch"
fi
}
clean_payment_backends() {
package_id=$1
backend_dir="$root_dir/$package_id/backend"
mkdir -p "$backend_dir"
rm -f \
"$backend_dir/provider" \
"$backend_dir"/provider-linux-* \
"$backend_dir"/provider-darwin-* \
"$backend_dir"/provider-windows-*.exe
rm -rf \
"$backend_dir/linux-amd64" \
"$backend_dir/linux-arm64" \
"$backend_dir/darwin-amd64" \
"$backend_dir/darwin-arm64" \
"$backend_dir/windows-amd64" \
"$backend_dir/windows-arm64"
}
build_payment_provider() {
package_id=$1
command_path=$2
output_file="$root_dir/$package_id/backend/provider"
goos=$3
goarch=$4
backend_dir="$root_dir/$package_id/backend"
artifact_name=$(payment_artifact_name "$goos" "$goarch")
output_file="$backend_dir/$artifact_name"
temporary_file="$output_file.tmp"
mkdir -p "$(dirname -- "$output_file")"
mkdir -p "$backend_dir"
rm -f "$temporary_file"
(
cd "$repo_dir/backend"
CGO_ENABLED="$payment_cgo" \
GOOS="$payment_goos" \
GOARCH="$payment_goarch" \
GOOS="$goos" \
GOARCH="$goarch" \
go build -trimpath -ldflags='-s -w' -o "$temporary_file" "$command_path"
)
mv "$temporary_file" "$output_file"
chmod 0755 "$output_file"
build_info=$(go version -m "$output_file")
printf '%s\n' "$build_info" | grep -F "GOOS=$payment_goos" >/dev/null
printf '%s\n' "$build_info" | grep -F "GOARCH=$payment_goarch" >/dev/null
printf '%s\n' "$build_info" | grep -F "GOOS=$goos" >/dev/null
printf '%s\n' "$build_info" | grep -F "GOARCH=$goarch" >/dev/null
printf '%s\n' "$build_info" | grep -F "CGO_ENABLED=$payment_cgo" >/dev/null
if [ "$goos/$goarch" = "$canonical_target" ]; then
cp "$output_file" "$backend_dir/provider"
chmod 0755 "$backend_dir/provider"
fi
}
build_payment_provider official-payment-wechat-native ./cmd/payment-wechat
build_payment_provider official-payment-alipay-page ./cmd/payment-alipay
for payment_plugin in $payment_plugins; do
clean_payment_backends "$payment_plugin"
done
for target in $payment_targets; do
target_goos=${target%/*}
target_goarch=${target#*/}
build_payment_provider official-payment-wechat-native ./cmd/payment-wechat "$target_goos" "$target_goarch"
build_payment_provider official-payment-alipay-page ./cmd/payment-alipay "$target_goos" "$target_goarch"
build_payment_provider official-payment-xunhupay ./cmd/payment-xunhupay "$target_goos" "$target_goarch"
done
package_plugin() {
package_id=$1

View File

@@ -1,3 +1,3 @@
# 支付宝电脑网站支付
官方 `yingce.payment/v1` 支付插件。后端入口由构建脚本编译为 `backend/provider`。
官方 `yingce.payment/v1` 支付插件。构建脚本交叉编译 macOS、Linux、Windows 的 amd64/arm64,入口仍声明为 `backend/provider`。

View File

@@ -93,5 +93,5 @@
}
]
},
"documentation": "# 支付宝电脑网站支付\n\n官方 `yingce.payment/v1` 支付插件。后端入口由构建脚本编译为 `backend/provider`。\n\n---\n\n## yingce.payment/v1\n\n支持 `validate_config`、`create_order`、`query_order`、`close_order`、`verify_notification` 和 `download_trade_bill`,统一返回 JSON 响应。\n\n<!-- YINGCE_MANIFEST_CONTRACT_START -->\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v1\",\n \"id\": \"official-payment-alipay-page\",\n \"name\": \"支付宝电脑网站支付\",\n \"version\": \"1.0.0\",\n \"author\": \"支付宝\",\n \"description\": \"支付宝电脑网站支付充值适配器。\",\n \"enabled\": true,\n \"installable\": true,\n \"runtime\": {\n \"backend\": \"rpc\",\n \"backendEntry\": \"backend/provider\"\n },\n \"surfaces\": [\n \"wallet\",\n \"settings\"\n ],\n \"permissions\": [\n \"payment.create\",\n \"payment.query\",\n \"payment.close\",\n \"payment.reconcile\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"publicBaseUrl\",\n \"type\": \"url\",\n \"label\": \"服务器公网地址\",\n \"required\": true\n },\n {\n \"name\": \"appId\",\n \"type\": \"string\",\n \"label\": \"应用 AppID\",\n \"required\": true\n },\n {\n \"name\": \"sellerId\",\n \"type\": \"string\",\n \"label\": \"支付宝商户 PID\",\n \"required\": true\n },\n {\n \"name\": \"merchantPrivateKey\",\n \"type\": \"textarea\",\n \"label\": \"应用私钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"alipayPublicKey\",\n \"type\": \"textarea\",\n \"label\": \"支付宝公钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"gateway\",\n \"type\": \"url\",\n \"label\": \"支付宝网关\",\n \"required\": true,\n \"default\": \"https://openapi.alipay.com/gateway.do\"\n }\n ]\n },\n \"contributes\": {\n \"paymentProviders\": [\n {\n \"id\": \"alipay-page-pay\",\n \"label\": \"支付宝电脑网站支付\",\n \"icon\": \"brand:alipay\",\n \"checkoutMode\": \"redirect\",\n \"identityFields\": [\n \"appId\",\n \"sellerId\"\n ],\n \"expiryPolicy\": {\n \"defaultMinutes\": 30,\n \"minMinutes\": 5,\n \"maxMinutes\": 1440\n },\n \"notificationSuccess\": {\n \"status\": 200,\n \"contentType\": \"text/plain; charset=utf-8\",\n \"body\": \"success\"\n },\n \"notificationFailure\": {\n \"status\": 400,\n \"contentType\": \"text/plain; charset=utf-8\",\n \"body\": \"failure\"\n }\n }\n ]\n },\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\"\n}\n```\n<!-- YINGCE_MANIFEST_CONTRACT_END -->\n"
"documentation": "# 支付宝电脑网站支付\n\n官方 `yingce.payment/v1` 支付插件。构建脚本交叉编译 macOS、Linux、Windows 的 amd64/arm64,入口仍声明为 `backend/provider`。\n\n---\n\n## yingce.payment/v1\n\n支持 `validate_config`、`create_order`、`query_order`、`close_order`、`verify_notification` 和 `download_trade_bill`,统一返回 JSON 响应。\n\n<!-- YINGCE_MANIFEST_CONTRACT_START -->\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v1\",\n \"id\": \"official-payment-alipay-page\",\n \"name\": \"支付宝电脑网站支付\",\n \"version\": \"1.0.0\",\n \"author\": \"支付宝\",\n \"description\": \"支付宝电脑网站支付充值适配器。\",\n \"enabled\": true,\n \"installable\": true,\n \"runtime\": {\n \"backend\": \"rpc\",\n \"backendEntry\": \"backend/provider\"\n },\n \"surfaces\": [\n \"wallet\",\n \"settings\"\n ],\n \"permissions\": [\n \"payment.create\",\n \"payment.query\",\n \"payment.close\",\n \"payment.reconcile\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"publicBaseUrl\",\n \"type\": \"url\",\n \"label\": \"服务器公网地址\",\n \"required\": true\n },\n {\n \"name\": \"appId\",\n \"type\": \"string\",\n \"label\": \"应用 AppID\",\n \"required\": true\n },\n {\n \"name\": \"sellerId\",\n \"type\": \"string\",\n \"label\": \"支付宝商户 PID\",\n \"required\": true\n },\n {\n \"name\": \"merchantPrivateKey\",\n \"type\": \"textarea\",\n \"label\": \"应用私钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"alipayPublicKey\",\n \"type\": \"textarea\",\n \"label\": \"支付宝公钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"gateway\",\n \"type\": \"url\",\n \"label\": \"支付宝网关\",\n \"required\": true,\n \"default\": \"https://openapi.alipay.com/gateway.do\"\n }\n ]\n },\n \"contributes\": {\n \"paymentProviders\": [\n {\n \"id\": \"alipay-page-pay\",\n \"label\": \"支付宝电脑网站支付\",\n \"icon\": \"brand:alipay\",\n \"checkoutMode\": \"redirect\",\n \"identityFields\": [\n \"appId\",\n \"sellerId\"\n ],\n \"expiryPolicy\": {\n \"defaultMinutes\": 30,\n \"minMinutes\": 5,\n \"maxMinutes\": 1440\n },\n \"notificationSuccess\": {\n \"status\": 200,\n \"contentType\": \"text/plain; charset=utf-8\",\n \"body\": \"success\"\n },\n \"notificationFailure\": {\n \"status\": 400,\n \"contentType\": \"text/plain; charset=utf-8\",\n \"body\": \"failure\"\n }\n }\n ]\n },\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\"\n}\n```\n<!-- YINGCE_MANIFEST_CONTRACT_END -->\n"
}

View File

@@ -1,3 +1,3 @@
# 微信支付 Native
官方 `yingce.payment/v1` 支付插件。后端入口由构建脚本编译为 `backend/provider`。
官方 `yingce.payment/v1` 支付插件。构建脚本交叉编译 macOS、Linux、Windows 的 amd64/arm64,入口仍声明为 `backend/provider`。

View File

@@ -101,5 +101,5 @@
}
]
},
"documentation": "# 微信支付 Native\n\n官方 `yingce.payment/v1` 支付插件。后端入口由构建脚本编译为 `backend/provider`。\n\n---\n\n## yingce.payment/v1\n\n支持 `validate_config`、`create_order`、`query_order`、`close_order`、`verify_notification` 和 `download_trade_bill`,统一返回 JSON 响应。\n\n<!-- YINGCE_MANIFEST_CONTRACT_START -->\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v1\",\n \"id\": \"official-payment-wechat-native\",\n \"name\": \"微信支付 Native\",\n \"version\": \"1.0.0\",\n \"author\": \"微信支付\",\n \"description\": \"微信支付 Native 扫码充值适配器。\",\n \"enabled\": true,\n \"installable\": true,\n \"runtime\": {\n \"backend\": \"rpc\",\n \"backendEntry\": \"backend/provider\"\n },\n \"surfaces\": [\n \"wallet\",\n \"settings\"\n ],\n \"permissions\": [\n \"payment.create\",\n \"payment.query\",\n \"payment.close\",\n \"payment.reconcile\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"publicBaseUrl\",\n \"type\": \"url\",\n \"label\": \"服务器公网地址\",\n \"required\": true\n },\n {\n \"name\": \"appId\",\n \"type\": \"string\",\n \"label\": \"AppID\",\n \"required\": true\n },\n {\n \"name\": \"mchId\",\n \"type\": \"string\",\n \"label\": \"商户号\",\n \"required\": true\n },\n {\n \"name\": \"merchantSerialNo\",\n \"type\": \"string\",\n \"label\": \"商户证书序列号\",\n \"required\": true\n },\n {\n \"name\": \"merchantPrivateKey\",\n \"type\": \"textarea\",\n \"label\": \"商户 API 私钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"apiV3Key\",\n \"type\": \"password\",\n \"label\": \"APIv3 密钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"wechatPayPublicKeyId\",\n \"type\": \"string\",\n \"label\": \"微信支付公钥 ID\",\n \"required\": true\n },\n {\n \"name\": \"wechatPayPublicKey\",\n \"type\": \"textarea\",\n \"label\": \"微信支付公钥\",\n \"required\": true,\n \"secret\": true\n }\n ]\n },\n \"contributes\": {\n \"paymentProviders\": [\n {\n \"id\": \"wechat-native\",\n \"label\": \"微信支付 Native\",\n \"icon\": \"brand:wechat-pay\",\n \"checkoutMode\": \"qr_code\",\n \"identityFields\": [\n \"appId\",\n \"mchId\"\n ],\n \"expiryPolicy\": {\n \"defaultMinutes\": 30,\n \"minMinutes\": 5,\n \"maxMinutes\": 1440\n },\n \"notificationSuccess\": {\n \"status\": 204\n },\n \"notificationFailure\": {\n \"status\": 400\n }\n }\n ]\n },\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\"\n}\n```\n<!-- YINGCE_MANIFEST_CONTRACT_END -->\n"
"documentation": "# 微信支付 Native\n\n官方 `yingce.payment/v1` 支付插件。构建脚本交叉编译 macOS、Linux、Windows 的 amd64/arm64,入口仍声明为 `backend/provider`。\n\n---\n\n## yingce.payment/v1\n\n支持 `validate_config`、`create_order`、`query_order`、`close_order`、`verify_notification` 和 `download_trade_bill`,统一返回 JSON 响应。\n\n<!-- YINGCE_MANIFEST_CONTRACT_START -->\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v1\",\n \"id\": \"official-payment-wechat-native\",\n \"name\": \"微信支付 Native\",\n \"version\": \"1.0.0\",\n \"author\": \"微信支付\",\n \"description\": \"微信支付 Native 扫码充值适配器。\",\n \"enabled\": true,\n \"installable\": true,\n \"runtime\": {\n \"backend\": \"rpc\",\n \"backendEntry\": \"backend/provider\"\n },\n \"surfaces\": [\n \"wallet\",\n \"settings\"\n ],\n \"permissions\": [\n \"payment.create\",\n \"payment.query\",\n \"payment.close\",\n \"payment.reconcile\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"publicBaseUrl\",\n \"type\": \"url\",\n \"label\": \"服务器公网地址\",\n \"required\": true\n },\n {\n \"name\": \"appId\",\n \"type\": \"string\",\n \"label\": \"AppID\",\n \"required\": true\n },\n {\n \"name\": \"mchId\",\n \"type\": \"string\",\n \"label\": \"商户号\",\n \"required\": true\n },\n {\n \"name\": \"merchantSerialNo\",\n \"type\": \"string\",\n \"label\": \"商户证书序列号\",\n \"required\": true\n },\n {\n \"name\": \"merchantPrivateKey\",\n \"type\": \"textarea\",\n \"label\": \"商户 API 私钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"apiV3Key\",\n \"type\": \"password\",\n \"label\": \"APIv3 密钥\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"wechatPayPublicKeyId\",\n \"type\": \"string\",\n \"label\": \"微信支付公钥 ID\",\n \"required\": true\n },\n {\n \"name\": \"wechatPayPublicKey\",\n \"type\": \"textarea\",\n \"label\": \"微信支付公钥\",\n \"required\": true,\n \"secret\": true\n }\n ]\n },\n \"contributes\": {\n \"paymentProviders\": [\n {\n \"id\": \"wechat-native\",\n \"label\": \"微信支付 Native\",\n \"icon\": \"brand:wechat-pay\",\n \"checkoutMode\": \"qr_code\",\n \"identityFields\": [\n \"appId\",\n \"mchId\"\n ],\n \"expiryPolicy\": {\n \"defaultMinutes\": 30,\n \"minMinutes\": 5,\n \"maxMinutes\": 1440\n },\n \"notificationSuccess\": {\n \"status\": 204\n },\n \"notificationFailure\": {\n \"status\": 400\n }\n }\n ]\n },\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\"\n}\n```\n<!-- YINGCE_MANIFEST_CONTRACT_END -->\n"
}

View File

@@ -0,0 +1,3 @@
# 虎皮椒聚合支付
官方 `yingce.payment/v1` 支付插件。构建脚本交叉编译 macOS、Linux、Windows 的 amd64/arm64,入口仍声明为 `backend/provider`。扫码模式返回虎皮椒 `url_qrcode` 图片地址,由钱包直接展示该图。

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<rect width="64" height="64" rx="16" fill="#E23C2F"/>
<path d="M18 40c6 8 22 8 28 0 2-8-4-16-14-22-10 6-16 14-14 22Z" fill="#F6C14A"/>
<path d="M32 14c2 4 3 8 2 12" stroke="#2F7D32" stroke-width="4" stroke-linecap="round"/>
<circle cx="32" cy="36" r="6" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

View File

@@ -0,0 +1,111 @@
## yingce.payment/v1
支持 `validate_config`、`create_order`、`query_order`、`close_order`、`verify_notification` 和 `download_trade_bill`,统一返回 JSON 响应。
虎皮椒渠道协议全部封装在本插件内:下单 `POST /payment/do.html`(JSON),查单 `POST /payment/query.html`,异步通知为 form,成功应答 `success`。配置字段包括 `publicBaseUrl`、`appId`、`appSecret`、`gateway`、`checkoutMode`。没有独立关单和交易账单下载接口:未支付关单按查单结果本地关闭,对账账单返回 not found。
<!-- YINGCE_MANIFEST_CONTRACT_START -->
## Manifest 完整接口定义
以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。
```json
{
"apiVersion": "yingce.plugin/v1",
"id": "official-payment-xunhupay",
"name": "虎皮椒聚合支付",
"version": "1.0.0",
"author": "虎皮椒",
"description": "虎皮椒聚合支付充值适配器,支持微信/支付宝扫码或跳转收银。",
"enabled": true,
"installable": true,
"runtime": {
"backend": "rpc",
"backendEntry": "backend/provider"
},
"surfaces": [
"wallet",
"settings"
],
"permissions": [
"payment.create",
"payment.query",
"payment.close",
"payment.reconcile"
],
"configuration": {
"fields": [
{
"name": "publicBaseUrl",
"type": "url",
"label": "服务器公网地址",
"required": true,
"description": "用于生成异步通知和同步返回地址,必须可被虎皮椒访问。"
},
{
"name": "appId",
"type": "string",
"label": "虎皮椒 APPID",
"required": true
},
{
"name": "appSecret",
"type": "password",
"label": "虎皮椒 APPSECRET",
"required": true,
"secret": true
},
{
"name": "gateway",
"type": "url",
"label": "支付网关",
"required": true,
"default": "https://api.xunhupay.com",
"description": "正式环境 https://api.xunhupay.com,备用 https://api.dpweixin.com。"
},
{
"name": "checkoutMode",
"type": "string",
"label": "收银方式",
"required": false,
"default": "qr_code",
"values": [
"qr_code",
"redirect"
],
"description": "qr_code 展示渠道返回的二维码图片 url_qrcode(约 5 分钟有效),钱包直接显示该图片;redirect 跳转手机端支付页 url。"
}
]
},
"contributes": {
"paymentProviders": [
{
"id": "xunhupay-aggregate",
"label": "虎皮椒聚合支付",
"icon": "assets/icon.svg",
"checkoutMode": "qr_code",
"identityFields": [
"appId"
],
"expiryPolicy": {
"defaultMinutes": 5,
"minMinutes": 5,
"maxMinutes": 1440
},
"notificationSuccess": {
"status": 200,
"contentType": "text/plain; charset=utf-8",
"body": "success"
},
"notificationFailure": {
"status": 400,
"contentType": "text/plain; charset=utf-8",
"body": "failure"
}
}
]
},
"documentation": "<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>"
}
```
<!-- YINGCE_MANIFEST_CONTRACT_END -->

View File

@@ -0,0 +1,97 @@
{
"apiVersion": "yingce.plugin/v1",
"id": "official-payment-xunhupay",
"name": "虎皮椒聚合支付",
"version": "1.0.0",
"author": "虎皮椒",
"description": "虎皮椒聚合支付充值适配器,支持微信/支付宝扫码或跳转收银。",
"enabled": true,
"installable": true,
"runtime": {
"backend": "rpc",
"backendEntry": "backend/provider"
},
"surfaces": [
"wallet",
"settings"
],
"permissions": [
"payment.create",
"payment.query",
"payment.close",
"payment.reconcile"
],
"configuration": {
"fields": [
{
"name": "publicBaseUrl",
"type": "url",
"label": "服务器公网地址",
"required": true,
"description": "用于生成异步通知和同步返回地址,必须可被虎皮椒访问。"
},
{
"name": "appId",
"type": "string",
"label": "虎皮椒 APPID",
"required": true
},
{
"name": "appSecret",
"type": "password",
"label": "虎皮椒 APPSECRET",
"required": true,
"secret": true
},
{
"name": "gateway",
"type": "url",
"label": "支付网关",
"required": true,
"default": "https://api.xunhupay.com",
"description": "正式环境 https://api.xunhupay.com,备用 https://api.dpweixin.com。"
},
{
"name": "checkoutMode",
"type": "string",
"label": "收银方式",
"required": false,
"default": "qr_code",
"values": [
"qr_code",
"redirect"
],
"description": "qr_code 展示渠道返回的二维码图片 url_qrcode(约 5 分钟有效),钱包直接显示该图片;redirect 跳转手机端支付页 url。"
}
]
},
"contributes": {
"paymentProviders": [
{
"id": "xunhupay-aggregate",
"label": "虎皮椒聚合支付",
"icon": "assets/icon.svg",
"checkoutMode": "qr_code",
"identityFields": [
"appId"
],
"expiryPolicy": {
"defaultMinutes": 5,
"minMinutes": 5,
"maxMinutes": 1440
},
"notificationSuccess": {
"status": 200,
"contentType": "text/plain; charset=utf-8",
"body": "success"
},
"notificationFailure": {
"status": 400,
"contentType": "text/plain; charset=utf-8",
"body": "failure"
}
}
]
},
"documentation": "# 虎皮椒聚合支付\n\n官方 `yingce.payment/v1` 支付插件。构建脚本交叉编译 macOS、Linux、Windows 的 amd64/arm64,入口仍声明为 `backend/provider`。扫码模式返回虎皮椒 `url_qrcode` 图片地址,由钱包直接展示该图。\n\n---\n\n## yingce.payment/v1\n\n支持 `validate_config`、`create_order`、`query_order`、`close_order`、`verify_notification` 和 `download_trade_bill`,统一返回 JSON 响应。\n\n虎皮椒渠道协议全部封装在本插件内:下单 `POST /payment/do.html`(JSON),查单 `POST /payment/query.html`,异步通知为 form,成功应答 `success`。配置字段包括 `publicBaseUrl`、`appId`、`appSecret`、`gateway`、`checkoutMode`。没有独立关单和交易账单下载接口:未支付关单按查单结果本地关闭,对账账单返回 not found。\n\n<!-- YINGCE_MANIFEST_CONTRACT_START -->\n## Manifest 完整接口定义\n\n以下 JSON 与插件包内实际 `manifest.json` 逐字段一致,覆盖插件身份、权限、配置、鉴权、参数、校验、创建、Agent、查询、取消、结果下载、响应和 Agent 响应映射。`documentation` 字段的值就是当前完整文档;为避免文档在自身内部无限递归,JSON 中仅用等义占位文本表示正文。\n\n```json\n{\n \"apiVersion\": \"yingce.plugin/v1\",\n \"id\": \"official-payment-xunhupay\",\n \"name\": \"虎皮椒聚合支付\",\n \"version\": \"1.0.0\",\n \"author\": \"虎皮椒\",\n \"description\": \"虎皮椒聚合支付充值适配器,支持微信/支付宝扫码或跳转收银。\",\n \"enabled\": true,\n \"installable\": true,\n \"runtime\": {\n \"backend\": \"rpc\",\n \"backendEntry\": \"backend/provider\"\n },\n \"surfaces\": [\n \"wallet\",\n \"settings\"\n ],\n \"permissions\": [\n \"payment.create\",\n \"payment.query\",\n \"payment.close\",\n \"payment.reconcile\"\n ],\n \"configuration\": {\n \"fields\": [\n {\n \"name\": \"publicBaseUrl\",\n \"type\": \"url\",\n \"label\": \"服务器公网地址\",\n \"required\": true,\n \"description\": \"用于生成异步通知和同步返回地址,必须可被虎皮椒访问。\"\n },\n {\n \"name\": \"appId\",\n \"type\": \"string\",\n \"label\": \"虎皮椒 APPID\",\n \"required\": true\n },\n {\n \"name\": \"appSecret\",\n \"type\": \"password\",\n \"label\": \"虎皮椒 APPSECRET\",\n \"required\": true,\n \"secret\": true\n },\n {\n \"name\": \"gateway\",\n \"type\": \"url\",\n \"label\": \"支付网关\",\n \"required\": true,\n \"default\": \"https://api.xunhupay.com\",\n \"description\": \"正式环境 https://api.xunhupay.com,备用 https://api.dpweixin.com。\"\n },\n {\n \"name\": \"checkoutMode\",\n \"type\": \"string\",\n \"label\": \"收银方式\",\n \"required\": false,\n \"default\": \"qr_code\",\n \"values\": [\n \"qr_code\",\n \"redirect\"\n ],\n \"description\": \"qr_code 展示渠道返回的二维码图片 url_qrcode(约 5 分钟有效),钱包直接显示该图片;redirect 跳转手机端支付页 url。\"\n }\n ]\n },\n \"contributes\": {\n \"paymentProviders\": [\n {\n \"id\": \"xunhupay-aggregate\",\n \"label\": \"虎皮椒聚合支付\",\n \"icon\": \"assets/icon.svg\",\n \"checkoutMode\": \"qr_code\",\n \"identityFields\": [\n \"appId\"\n ],\n \"expiryPolicy\": {\n \"defaultMinutes\": 5,\n \"minMinutes\": 5,\n \"maxMinutes\": 1440\n },\n \"notificationSuccess\": {\n \"status\": 200,\n \"contentType\": \"text/plain; charset=utf-8\",\n \"body\": \"success\"\n },\n \"notificationFailure\": {\n \"status\": 400,\n \"contentType\": \"text/plain; charset=utf-8\",\n \"body\": \"failure\"\n }\n }\n ]\n },\n \"documentation\": \"<当前插件的完整 documentation,由 README.md 与 docs/interface.md 拼接而成;为避免 JSON 递归,此处不重复展开正文。>\"\n}\n```\n<!-- YINGCE_MANIFEST_CONTRACT_END -->\n"
}

View File

@@ -2,17 +2,31 @@
set -eu
root_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
require_all=false
if [ "${1:-}" = "--all" ]; then
require_all=true
shift
fi
expected_goos=${1:-}
expected_goarch=${2:-}
if [ "$expected_goos" != "linux" ]; then
echo "payment package verification currently requires expected GOOS linux" >&2
if [ -z "$expected_goos" ] || [ -z "$expected_goarch" ]; then
echo "usage: $0 [--all] <goos> <goarch>" >&2
exit 2
fi
case "$expected_goos" in
linux|darwin|windows) ;;
*)
echo "unsupported payment package OS: $expected_goos" >&2
exit 2
;;
esac
case "$expected_goarch" in
amd64) expected_machine=3e00 ;;
arm64) expected_machine=b700 ;;
amd64|arm64) ;;
*)
echo "unsupported payment package architecture: $expected_goarch" >&2
exit 2
@@ -22,44 +36,132 @@ esac
temporary_dir=$(mktemp -d)
trap 'rm -rf "$temporary_dir"' EXIT HUP INT TERM
for package_id in official-payment-wechat-native official-payment-alipay-page; do
provider="$root_dir/$package_id/backend/provider"
file_magic() {
od -An -tx1 -N4 "$1" | tr -d '[:space:]'
}
payment_artifact_name() {
goos=$1
goarch=$2
if [ "$goos" = "windows" ]; then
printf 'provider-%s-%s.exe\n' "$goos" "$goarch"
else
printf 'provider-%s-%s\n' "$goos" "$goarch"
fi
}
verify_platform_magic() {
path=$1
goos=$2
goarch=$3
magic=$(file_magic "$path")
case "$goos" in
linux)
if [ "$magic" != "7f454c46" ]; then
echo "$path is not an ELF executable (magic=$magic)" >&2
return 1
fi
class=$(od -An -tx1 -j4 -N1 "$path" | tr -d '[:space:]')
byte_order=$(od -An -tx1 -j5 -N1 "$path" | tr -d '[:space:]')
if [ "$class" != "02" ] || [ "$byte_order" != "01" ]; then
echo "$path is not a 64-bit little-endian ELF executable" >&2
return 1
fi
expected_machine=3e00
if [ "$goarch" = "arm64" ]; then
expected_machine=b700
fi
machine=$(od -An -tx1 -j18 -N2 "$path" | tr -d '[:space:]')
if [ "$machine" != "$expected_machine" ]; then
echo "$path architecture does not match linux/$goarch (ELF machine=$machine)" >&2
return 1
fi
;;
darwin)
case "$magic" in
cffaedfe|cefaedfe|feedfacf|feedface|cafebabe|bebafeca) ;;
*)
echo "$path is not a Mach-O executable (magic=$magic)" >&2
return 1
;;
esac
;;
windows)
mz=$(od -An -tx1 -N2 "$path" | tr -d '[:space:]')
if [ "$mz" != "4d5a" ]; then
echo "$path is not a Windows PE executable (magic=$mz)" >&2
return 1
fi
;;
esac
}
verify_packaged_file() {
package_file=$1
relative_path=$2
source_file=$3
archived_file=$4
unzip -p "$package_file" "$relative_path" >"$archived_file"
if ! cmp -s "$source_file" "$archived_file"; then
echo "$relative_path in $package_file differs from the directory file" >&2
return 1
fi
}
all_targets="linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64"
verify_targets="$expected_goos/$expected_goarch"
if [ "$require_all" = true ]; then
verify_targets="$all_targets"
if [ "$expected_goos/$expected_goarch" != "linux/amd64" ]; then
echo "--all currently expects canonical linux/amd64 as the host verification target" >&2
exit 2
fi
fi
for package_id in official-payment-wechat-native official-payment-alipay-page official-payment-xunhupay; do
backend_dir="$root_dir/$package_id/backend"
provider="$backend_dir/provider"
package_file="$root_dir/$package_id.yingce-plugin"
archived_provider="$temporary_dir/$package_id-provider"
expected_artifact=$(payment_artifact_name "$expected_goos" "$expected_goarch")
test -f "$provider"
if [ "$(uname -s)" = "Linux" ]; then
test -f "$package_file"
if [ "$(uname -s)" = "Linux" ] || [ "$(uname -s)" = "Darwin" ]; then
test -x "$provider"
fi
test -f "$package_file"
magic=$(od -An -tx1 -N4 "$provider" | tr -d '[:space:]')
if [ "$magic" != "7f454c46" ]; then
echo "$package_id provider is not an ELF executable (magic=$magic)" >&2
verify_platform_magic "$provider" "$expected_goos" "$expected_goarch"
verify_packaged_file "$package_file" backend/provider "$provider" "$temporary_dir/$package_id-provider"
test -f "$backend_dir/$expected_artifact"
if ! cmp -s "$provider" "$backend_dir/$expected_artifact"; then
echo "$package_id backend/provider does not match $expected_artifact" >&2
exit 1
fi
verify_packaged_file "$package_file" "backend/$expected_artifact" "$backend_dir/$expected_artifact" "$temporary_dir/$package_id-$expected_artifact"
class=$(od -An -tx1 -j4 -N1 "$provider" | tr -d '[:space:]')
byte_order=$(od -An -tx1 -j5 -N1 "$provider" | tr -d '[:space:]')
if [ "$class" != "02" ] || [ "$byte_order" != "01" ]; then
echo "$package_id provider is not a 64-bit little-endian ELF executable" >&2
exit 1
fi
machine=$(od -An -tx1 -j18 -N2 "$provider" | tr -d '[:space:]')
if [ "$machine" != "$expected_machine" ]; then
echo "$package_id provider architecture does not match linux/$expected_goarch (ELF machine=$machine)" >&2
exit 1
fi
unzip -p "$package_file" backend/provider >"$archived_provider"
if ! cmp -s "$provider" "$archived_provider"; then
echo "$package_id directory provider and packaged provider differ" >&2
exit 1
fi
for target in $verify_targets; do
target_goos=${target%/*}
target_goarch=${target#*/}
artifact_name=$(payment_artifact_name "$target_goos" "$target_goarch")
artifact_path="$backend_dir/$artifact_name"
test -f "$artifact_path"
verify_platform_magic "$artifact_path" "$target_goos" "$target_goarch"
verify_packaged_file "$package_file" "backend/$artifact_name" "$artifact_path" "$temporary_dir/$package_id-$artifact_name"
done
if [ "${PAYMENT_PLUGIN_SMOKE_TEST:-0}" = "1" ]; then
response=$(printf 'invalid-json\n' | "$provider")
printf '%s\n' "$response" | grep -F '"code":"invalid_request"' >/dev/null
current_sys=$(uname -s)
can_smoke=0
if [ "$expected_goos" = "linux" ] && [ "$current_sys" = "Linux" ]; then
can_smoke=1
fi
if [ "$expected_goos" = "darwin" ] && [ "$current_sys" = "Darwin" ]; then
can_smoke=1
fi
if [ "$can_smoke" = "1" ]; then
response=$(printf 'invalid-json\n' | "$provider")
printf '%s\n' "$response" | grep -F '"code":"invalid_request"' >/dev/null
fi
fi
done