| 组件 | 技术选型 |
|---|---|
| 后端框架 | FastAPI 0.115.2 |
| 数据库 | PostgreSQL 14+ (asyncpg) / MySQL (asyncmy) |
| ORM | SQLAlchemy 2.0.45 |
| 支付宝SDK | alipay-sdk-python >= 3.7.1018 |
| 异步框架 | uvicorn + asyncio |
alipay-sdk-python >= 3.7.1018
├── alipay.aop.api.AlipayClientConfig # 配置类
├── alipay.aop.api.DefaultAlipayClient # 客户端类
├── alipay.aop.api.domain.* # 业务模型类
├── alipay.aop.api.request.* # 请求类
└── alipay.aop.api.response.* # 响应类
app/core/alipay/ # 支付宝 SDK 封装
├── __init__.py # 导出接口
├── config.py # SDK 配置类
├── client.py # 统一客户端
├── schema.py # 响应模型定义
└── services/ # 服务层(规划中)
config.pyfrom pydantic_settings import BaseSettings, SettingsConfigDict
class AlipayConfig(BaseSettings):
"""支付宝 SDK 配置"""
app_id: str = Field(default="", validation_alias=AliasChoices("ALIPAY_APPID", "ALIPAY_APP_ID"))
app_private_key: str = Field(default="")
alipay_public_key: str = Field(default="")
format: str = Field(default="json")
charset: str = Field(default="UTF-8")
sign_type: str = Field(default="RSA2")
sandbox: bool = Field(default=False)
notify_url: str = Field(default="")
return_url: str = Field(default="")
@property
def is_valid(self) -> bool:
"""检查配置是否有效"""
return bool(self.app_id and self.app_private_key and self.alipay_public_key)
def to_alipay_client_config(self) -> "AlipayClientConfig":
"""转换为官方 AlipayClientConfig"""
config = AlipayClientConfig(sandbox_debug=self.sandbox)
config.app_id = self.app_id
config.app_private_key = self.app_private_key
config.alipay_public_key = self.alipay_public_key
config.format = self.format
config.charset = self.charset
config.sign_type = self.sign_type
return config
client.pyfrom alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
class AlipayClient:
"""支付宝客户端工具类"""
_client: DefaultAlipayClient | None = None
@classmethod
def get_client(cls) -> DefaultAlipayClient:
"""获取支付宝客户端实例"""
if cls._client is None:
cls._client = DefaultAlipayClient(
alipay_client_config=get_alipay_config().to_alipay_client_config(),
logger=logger,
)
return cls._client
schema.pyclass AlipayResponse(BaseModel):
"""支付宝响应基础模型"""
code: str = Field(..., description="网关返回码,10000表示成功")
msg: str = Field(..., description="网关返回码描述")
sub_code: Optional[str] = Field(None, description="业务错误码")
sub_msg: Optional[str] = Field(None, description="业务错误描述")
@property
def success(self) -> bool:
return self.code == AlipayResponseCodeEnum.SUCCESS.value.code
AlipayResponseCode = NamedTuple("AlipayResponseCode", [("code", str), ("msg", str)])
class AlipayResponseCodeEnum(Enum):
"""支付宝响应码枚举"""
SUCCESS = AlipayResponseCode("10000", "接口调用成功")
SERVICE_UNAVAILABLE = AlipayResponseCode("20000", "服务不可用")
AUTH_INSUFFICIENT = AlipayResponseCode("20001", "授权权限不足")
MISSING_REQUIRED_PARAM = AlipayResponseCode("40001", "缺少必选参数")
INVALID_PARAM = AlipayResponseCode("40002", "非法的参数")
CONDITION_ABNORMAL = AlipayResponseCode("40003", "条件异常")
SERVICE_NOT_EXIST = AlipayResponseCode("40004", "服务不存在")
METHOD_NOT_SUPPORTED = AlipayResponseCode("40005", "不支持请求方式")
PERMISSION_DENIED = AlipayResponseCode("40006", "权限不足")
@classmethod
def from_code(cls, code: str) -> AlipayResponseCode:
"""根据 code 获取对应的 AlipayResponseCode"""
for item in cls:
if item.value.code == code:
return item.value
return AlipayResponseCode(code, "未知错误")
class AlipayCommerceEcEnterpriseRegisterInviteCreateResponse(AlipayResponse):
"""邀请企业注册响应"""
pc_invite_url: Optional[str] = Field(None, description="企业注册/认证/签约三合一页面的链接地址")
expire_time: Optional[datetime] = Field(None, description="链接过期时间")
@field_validator("expire_time", mode="before")
@classmethod
def parse_expire_time(cls, v: Optional[str]) -> Optional[datetime]:
if v is None:
return None
return datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
from app.core.alipay import AlipayClient, get_alipay_config
# 获取客户端
client = AlipayClient.get_client()
# 构建请求
model = AlipayCommerceEcEnterpriseRegisterInviteCreateModel()
model.out_biz_no = "2024051000000001"
model.identity_type = "ALIPAY_USER_ID"
model.identity = "2088051553855663"
model.register_mode = "NORMAL"
model.sign_fund_way = "BALANCE"
request = AlipayCommerceEcEnterpriseRegisterInviteCreateRequest()
request.biz_model = model
# 执行调用
response = client.execute(request)
# 使用 Pydantic 模型验证响应
result = AlipayCommerceEcEnterpriseRegisterInviteCreateResponse.model_validate_json(response)
if result.success:
print(result.pc_invite_url)
print(result.expire_time)
from app.core.alipay.schema import AlipayResponseCodeEnum
# 获取成功码信息
success_code = AlipayResponseCodeEnum.SUCCESS.value
print(success_code.code) # "10000"
print(success_code.msg) # "接口调用成功"
# 根据 code 反查
result = AlipayResponseCodeEnum.from_code("40001")
print(result.code) # "40001"
print(result.msg) # "缺少必选参数"
# app/core/alipay/__init__.py
from app.core.alipay.client import AlipayClient
from app.core.alipay.config import AlipayConfig, get_alipay_config
__all__ = [
"AlipayConfig",
"get_alipay_config",
"AlipayClient",
]
services/
├── base.py # 基础服务类
├── enterprise_service.py # 企业服务
├── employee_service.py # 员工服务
├── expense_service.py # 费控服务
├── quota_service.py # 额度服务
└── bill_service.py # 账单服务
class AlipayError(Exception):
"""支付宝基础异常"""
code: str = "500"
msg: str = "Unknown error"
class AlipayApiError(AlipayError):
"""API 调用错误"""
code = "40000"
class AlipayNetworkError(AlipayError):
"""网络错误(可重试)"""
code = "20000"
基于指数退避的重试策略,支持的错误码:
20000 - 服务不可用20001 - 授权权限不足ACQ.SystemError - 银行系统异常sandbox=Truecode 和 sub_code 进行分类处理