ForcePilot/backend/package/yuxi/channel/extensions/alipay/config.py
Kris 71364ea579 feat(alipay): 新增支付宝渠道插件完整实现
实现了支付宝生活号渠道的完整功能,包括消息接收回调、发送、安全验证、去重、配对绑定、流式回复支持等完整能力。
2026-05-21 10:39:23 +08:00

170 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
from yuxi.channel.extensions.alipay.types import AlipayAccount, AlipayDmPolicy, AlipayMode
class AlipayConfig:
ENV_MAP = {
"app_id": "ALIPAY_APP_ID",
"app_private_key": "ALIPAY_PRIVATE_KEY",
"alipay_public_key": "ALIPAY_PUBLIC_KEY",
"aes_key": "ALIPAY_AES_KEY",
"mode": "ALIPAY_MODE",
"dm_policy": "ALIPAY_DM_POLICY",
"subscribe_msg": "ALIPAY_SUBSCRIBE_MSG",
"remove_markdown": "ALIPAY_REMOVE_MD",
}
def list_account_ids(self, config: dict | None = None) -> list[str]:
if self._resolve_app_id():
return ["default"]
return []
def resolve_account(self, account_id: str = "default") -> AlipayAccount:
app_id = self._resolve_app_id()
return AlipayAccount(
account_id=account_id,
app_id=app_id or "",
app_private_key=self._env_or_config("app_private_key") or "",
alipay_public_key=self._env_or_config("alipay_public_key") or "",
aes_key=self._env_or_config("aes_key"),
mode=AlipayMode(self._env_or_config("mode") or "production"),
gateway_url=(
"https://openapi.alipaydev.com/gateway.do"
if self._env_or_config("mode") == "sandbox"
else "https://openapi.alipay.com/gateway.do"
),
name=self._env_or_config("name") or "",
enabled=self._env_or_config("enabled") != "false",
dm_policy=AlipayDmPolicy(self._env_or_config("dm_policy") or "open"),
allow_from=[],
remove_markdown=self._env_or_config("remove_markdown") != "false",
subscribe_msg=self._env_or_config("subscribe_msg") or "",
)
def is_configured(self, account: dict | None = None) -> bool:
return bool(
self._resolve_app_id()
and self._env_or_config("app_private_key")
and self._env_or_config("alipay_public_key")
)
def _resolve_app_id(self) -> str | None:
return self._env_or_config("app_id")
@staticmethod
def _env_or_config(key: str) -> str | None:
env_key = AlipayConfig.ENV_MAP.get(key, "")
if env_key:
val = os.getenv(env_key)
if val:
return val
return None
def config_schema(self) -> dict:
return {
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"title": "支付宝渠道配置",
"properties": {
"app_id": {
"type": "string",
"title": "AppID",
"description": "支付宝开放平台应用 AppID",
},
"app_private_key": {
"type": "string",
"title": "应用私钥",
"x-ui-password": True,
"description": "RSA2 应用私钥 (PEM 格式)",
},
"alipay_public_key": {
"type": "string",
"title": "支付宝公钥",
"description": "支付宝公钥 (PEM 格式)",
},
"aes_key": {
"type": "string",
"title": "AES 解密密钥",
"x-ui-password": True,
"description": "AES 密钥可选16 字符)",
},
"mode": {
"type": "string",
"enum": ["production", "sandbox"],
"default": "production",
"title": "运行模式",
},
"dm_policy": {
"type": "string",
"enum": ["open", "pairing", "allowlist", "disabled"],
"default": "open",
"title": "DM 策略",
},
"subscribe_msg": {
"type": "string",
"title": "关注欢迎语",
"description": "用户关注生活号后自动回复的文本",
},
"remove_markdown": {
"type": "boolean",
"default": True,
"title": "移除 Markdown 格式",
"description": "自动移除 AI 回复中的 Markdown 符号",
},
},
"required": ["app_id", "app_private_key", "alipay_public_key"],
}
def is_enabled(self, account: dict | None = None, config: dict | None = None) -> bool:
if account is None:
return self._env_or_config("enabled") != "false"
return account.get("enabled", True)
def disabled_reason(self, account: dict | None = None, config: dict | None = None) -> str:
if not account:
return "未配置账户"
if not account.get("enabled", True):
return "账户已被禁用"
return ""
def unconfigured_reason(self, account: dict | None = None, config: dict | None = None) -> str:
if not account:
return "未配置账户"
missing = []
if not account.get("app_id"):
missing.append("AppId")
if not account.get("app_private_key"):
missing.append("应用私钥")
if not account.get("alipay_public_key"):
missing.append("支付宝公钥")
return f"缺少: {', '.join(missing)}" if missing else ""
def describe_account(self, account: dict | None = None, config: dict | None = None) -> dict:
if not account:
return {"account_id": ""}
return {
"account_id": account.get("account_id", "default"),
"app_id": account.get("app_id", ""),
"name": account.get("name", ""),
"mode": account.get("mode", "production"),
"dm_policy": account.get("dm_policy", "open"),
"enabled": account.get("enabled", True),
}
def resolve_allow_from(self, config: dict, account_id: str | None = None) -> list[str] | None:
account = self.resolve_account(account_id or "default")
return account.allow_from or None
def format_allow_from(self, config: dict, account_id: str | None, allow_from: list) -> list[str]:
return [str(item) for item in allow_from]
def has_configured_state(self, config: dict | None = None) -> bool:
return self.is_configured()
def has_persisted_auth_state(self, config: dict | None = None) -> bool:
return bool(self._resolve_app_id() and self._env_or_config("app_private_key"))
def default_account_id(self, config: dict | None = None) -> str:
return "default"