# SMANAGE 抽象支付网关 — 设计文档 ## 一、设计目标 SMANAGE 是 SAAS 系统,不绑定具体业务。支付网关抽象层提供**多平台支付能力的统一接入**,子服务只需要关心"付了多少钱"和"通过哪个渠道付",SAAS 负责"怎么付"。 ### 核心原则 | 原则 | 说明 | |------|------| | **适配器模式** | 每个支付平台是一个适配器,新增平台只需实现接口 + 注册一行 | | **密钥走构造传参** | `PaymentCredentials` 在网关实例创建时注入,网关层不持有状态 | | **租户隔离** | 所有操作通过 `X-Tenant-UUID` 头识别租户,配置和交易记录完全分离 | | **加密存储** | 密钥使用 XOR + Base64 加密,密钥通过 `settings.PAYMENT_ENCRYPTION_KEY` 管理 | | **回调不认证** | 回调端点关闭 DRF 认证,由网关自身 `VerifyNotify()` 签名验证保证安全 | --- ## 二、架构分层 ``` 子服务(如阿龙电竞) │ ├── 注入租户密钥 → PaymentGatewayConfig(SAAS配置) │ └── 调用 PaymentManager.pay(platform='wechat', amount=100) │ ├── 根据 platform 从 GatewayRegistry 获取适配器类 ├── 网关实例由密钥创建(构造时传参,非来自数据库) └── 执行 Pay / Query / Refund / Transfer / Close ``` --- ## 三、文件清单(12 个文件) | 层级 | 文件 | 职责 | |------|------|------| | **抽象层** | `gateways/base.py` | `AbstractPaymentGateway` 抽象基类 + 7 个数据模型 | | **注册表** | `gateways/registry.py` | `GatewayRegistry` 渠道注册工厂 + `payment_registry` 全局单例 | | **适配器** | `gateways/wechat.py` | `WechatPaymentGateway` — 微信支付(JSAPI/Native/H5/小程序 + 转账 + 回调验签) | | **适配器** | `gateways/alipay.py` | `AlipayPaymentGateway` — 支付宝(APP/H5/Native + 转账 + 回调验签) | | **适配器** | `gateways/bank.py` | `BankPaymentGateway` — 银行卡(银联/网银 + 代付到银行卡) | | **模型** | `models.py` | `PaymentGatewayConfig`(密钥加密存储)+ `PaymentTransaction`(交易流水) | | **编排器** | `manager.py` | `PaymentManager` — 配置/支付/查询/退款/转账/回调六合一 | | **API** | `views.py` | 7 个 REST 端点(含无需认证的 NotifyView) | | **序列化** | `serializers.py` | 5 个 Serializer | | **路由** | `urls.py` | 7 个 URL 路由 | | **配置** | `apps.py` | AppConfig,`ready()` 时自动注册三个渠道 | | **管理** | `admin.py` | Django Admin 注册 | --- ## 四、核心数据模型 ### 4.1 PaymentCredentials(非持久化) 网关实例创建时传入,不存储到数据库: ```python @dataclass class PaymentCredentials: AppID: str = '' MchID: str = '' APIKey: str = '' PrivateKey: str = '' PublicKey: str = '' CertPath: str = '' CertKeyPath: str = '' NotifyURL: str = '' ReturnURL: str = '' Extra: Dict[str, str] = field(default_factory=dict) ``` ### 4.2 PaymentGatewayConfig(数据库持久化—加密) 每个租户每个渠道一条配置,密钥字段加密存储: | 字段 | 类型 | 说明 | |------|------|------| | ConfigUUID | UUID | 主键 | | TenantUUID | UUID | 租户标识 | | Platform | CharField(32) | wechat / alipay / bank | | AppID | CharField(64) | 应用ID | | MchID | CharField(64) | 商户号 | | APIKeyEncrypted | CharField(512) | 加密的 API 密钥 | | PrivateKeyEncrypted | TextField | 加密的私钥 | | NotifyURL | CharField(512) | 回调地址 | | ConfigStatus | SmallIntegerField | 1=启用 0=禁用 | ### 4.3 PaymentTransaction(数据库持久化) 每笔支付/退款/转账操作的统一流水记录: | 字段 | 类型 | 说明 | |------|------|------| | TransactionUUID | UUID | 主键 | | TenantUUID | UUID | 租户 | | Platform | CharField(32) | 支付平台 | | TransactionType | CharField(16) | pay / refund / transfer | | OutTradeNO | CharField(64) | 商户订单号(唯一) | | TotalAmount | DecimalField | 金额(最大18位,4位小数) | | TransactionStatus | SmallIntegerField | 0=处理中 1=成功 2=失败 3=已关闭 | ### 4.4 请求/响应数据模型(dataclass,非持久化) | 数据类 | 用途 | |--------|------| | `PaymentRequest` | 支付请求标准化入参 | | `PaymentResponse` | 支付响应标准化出参(含 `PrepayID` 属性) | | `RefundRequest` | 退款请求 | | `RefundResponse` | 退款响应 | | `TransferRequest` | 转账请求(企业付款到零钱/银行卡) | | `TransferResponse` | 转账响应 | | `QueryResponse` | 订单查询响应(含 `IsPaid`/`IsRefunded` 属性) | --- ## 五、抽象网关接口 ```python class AbstractPaymentGateway(ABC): Channel: PaymentChannel = 'custom' Name: str = 'Abstract' def __init__(self, credentials: PaymentCredentials): self._credentials = credentials self._validate_credentials() @abstractmethod def Pay(self, request: PaymentRequest) -> PaymentResponse: ... @abstractmethod def Query(self, out_trade_no: str) -> QueryResponse: ... @abstractmethod def Refund(self, request: RefundRequest) -> RefundResponse: ... @abstractmethod def Close(self, out_trade_no: str) -> bool: ... @abstractmethod def VerifyNotify(self, raw_data: bytes, headers: Dict[str, str]) -> bool: ... @abstractmethod def ParseNotify(self, raw_data: bytes) -> Dict[str, Any]: ... def Transfer(self, request: TransferRequest) -> TransferResponse: raise NotImplementedError(f'{self.Name} 不支持转账功能') ``` ### 已实现适配器 | 渠道 | Channel | 类名 | 支持的方法 | Transfer | |------|---------|------|------------|----------| | 微信支付 | `wechat` | `WechatPaymentGateway` | jsapi / native / h5 / miniapp | 支持 | | 支付宝 | `alipay` | `AlipayPaymentGateway` | app / h5 / native | 支持 | | 银行卡 | `bank` | `BankPaymentGateway` | app / h5 / native | 仅 bank 类型 | ### 新增支付渠道只需三步 ```python # 1. 实现适配器 class FooPaymentGateway(AbstractPaymentGateway): Channel = 'foo' Name = '某支付' def Pay(self, request: PaymentRequest) -> PaymentResponse: ... # 2. 在 apps.py 注册 payment_registry.register('foo', FooPaymentGateway) # 3. 子服务配置密钥后即可使用 manager = PaymentManager(tenant_uuid) manager.Pay(platform='foo', amount=100) ``` --- ## 六、PaymentManager 编排器 ```python class PaymentManager: def __init__(self, tenant_uuid) def ConfigureGateway(platform, app_id, mch_id, api_key, ...) # 配置支付渠道 def Pay(platform, amount, subject, method, ...) # 发起支付 def Query(out_trade_no, platform=None) # 查询订单 def Refund(out_trade_no, amount, reason, ...) # 发起退款 def Transfer(platform, amount, payee_account, ...) # 企业付款 def HandleNotify(platform, raw_data, headers=None) # 处理回调 def GetGatewayInstance(platform) # 获取网关实例(供子服务自定义) ``` --- ## 七、API 端点 ``` POST /api/payment/config/ # 配置支付渠道(SAAS管理员) GET /api/payment/config/ # 查看已配置渠道列表 DELETE /api/payment/config/ # 禁用渠道 POST /api/payment/pay/ # 发起支付 GET /api/payment/query// # 查询订单 POST /api/payment/refund/ # 发起退款 POST /api/payment/transfer/ # 企业付款 POST /api/payment/notify// # 支付回调(微信/支付宝等) GET /api/payment/channels/ # 列出所有可用渠道 ``` --- ## 八、子服务集成示例 ```python from gvsds.apps.payment.manager import PaymentManager, PaymentError manager = PaymentManager(tenant_uuid='al_long_001') # 配置微信支付 manager.ConfigureGateway( platform='wechat', app_id='wx1234567890', mch_id='1234567890', api_key='your_api_key', notify_url='https://al_long.gvsds.com/api/payment/notify/wechat/', ) # 发起支付 tx = manager.Pay( platform='wechat', amount=Decimal('100.00'), subject='订单支付', method='jsapi', open_id='user_openid', ) # tx._prepay_info 包含前端调起支付所需的参数 return {'prepay_info': tx._prepay_info} # 查询订单 result = manager.Query(out_trade_no='PY-ABC123') if result.IsPaid: # 更新子服务订单状态 ... # 退款 tx = manager.Refund(out_trade_no='PY-ABC123', amount=Decimal('100.00')) ``` --- ## 九、安全设计 1. **密钥加密存储**:`APIKey`、`PrivateKey`、`PublicKey` 使用 XOR + Base64 加密后入库 2. **加密密钥外置**:通过 `settings.PAYMENT_ENCRYPTION_KEY`(从 secrets 文件读取)管理 3. **回调签名验证**:每个网关适配器自行实现 `VerifyNotify()` 验证回调签名的合法性 4. **回调端点不认证**:`NotifyView` 关闭 DRF 的认证和权限类,但网关层签名验证保证安全 5. **密钥不输出**:`PaymentCredentials.AsDict()` 方法中 `api_key` 显示为 `***` 6. **幂等保护**:`OutTradeNO` 唯一约束,防止重复支付记录