|
|
@@ -0,0 +1,680 @@
|
|
|
+# 因公付款服务系统 - Plugin 模块规划
|
|
|
+
|
|
|
+## 一、规划背景
|
|
|
+
|
|
|
+基于 [因公付款服务系统架构设计.md](../docs/因公付款服务系统架构设计.md) 文档,本规划旨在将因公付款服务的核心功能模块落地到 `app/plugin/` 目录下,采用 `module_payment` 作为一级模块,下设多个二级子模块。
|
|
|
+
|
|
|
+## 二、目录结构设计
|
|
|
+
|
|
|
+```
|
|
|
+app/plugin/
|
|
|
+├── module_payment/ # 因公付款服务一级模块
|
|
|
+│ │
|
|
|
+│ ├── plugin.toml # 模块元数据
|
|
|
+│ │
|
|
|
+│ ├── enterprise/ # 企业管理子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py # 企业实体定义
|
|
|
+│ │ ├── schema.py # Pydantic请求/响应模型
|
|
|
+│ │ ├── crud.py # 数据库CRUD操作
|
|
|
+│ │ ├── service.py # 业务逻辑层
|
|
|
+│ │ ├── controller.py # API路由控制器
|
|
|
+│ │ └── utils.py # 工具函数
|
|
|
+│ │
|
|
|
+│ ├── department/ # 部门管理子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ └── controller.py
|
|
|
+│ │
|
|
|
+│ ├── employee/ # 员工管理子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ ├── controller.py
|
|
|
+│ │ └── utils.py
|
|
|
+│ │
|
|
|
+│ ├── expense_institution/ # 费控制度子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ └── controller.py
|
|
|
+│ │
|
|
|
+│ ├── expense_rule/ # 使用规则子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ └── controller.py
|
|
|
+│ │
|
|
|
+│ ├── quota/ # 额度管理子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ └── controller.py
|
|
|
+│ │
|
|
|
+│ ├── bill/ # 账单管理子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ ├── controller.py
|
|
|
+│ │ └── utils.py
|
|
|
+│ │
|
|
|
+│ ├── voucher/ # 凭证管理子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── model.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── crud.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ └── controller.py
|
|
|
+│ │
|
|
|
+│ ├── payment/ # 支付跳转子模块
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── schema.py
|
|
|
+│ │ ├── service.py
|
|
|
+│ │ ├── controller.py
|
|
|
+│ │ └── utils.py # 支付宝跳转链接生成工具
|
|
|
+│ │
|
|
|
+│ └── notification/ # 消息通知子模块
|
|
|
+│ ├── __init__.py
|
|
|
+│ ├── handlers/ # 通知处理器
|
|
|
+│ │ ├── __init__.py
|
|
|
+│ │ ├── enterprise_handler.py # 企业状态变更处理
|
|
|
+│ │ ├── employee_handler.py # 员工变更处理
|
|
|
+│ │ ├── bill_handler.py # 账单变动处理
|
|
|
+│ │ └── voucher_handler.py # 凭证变动处理
|
|
|
+│ ├── service.py
|
|
|
+│ └── controller.py # WebSocket/HTTP回调入口
|
|
|
+│
|
|
|
+├── module_ai/ # 现有AI模块(保留)
|
|
|
+├── module_example/ # 现有示例模块(保留)
|
|
|
+├── module_generator/ # 现有代码生成模块(保留)
|
|
|
+└── module_task/ # 现有任务调度模块(保留)
|
|
|
+```
|
|
|
+
|
|
|
+## 三、模块职责划分
|
|
|
+
|
|
|
+### 3.1 module_payment/plugin.toml 配置
|
|
|
+
|
|
|
+```toml
|
|
|
+name = "payment"
|
|
|
+title = "因公付款服务"
|
|
|
+version = "1.0.0"
|
|
|
+description = "基于支付宝企业码的因公付款服务系统核心模块"
|
|
|
+optional = false
|
|
|
+tags = ["payment", "enterprise", "expense", "alipay"]
|
|
|
+```
|
|
|
+
|
|
|
+### 3.2 各子模块详细职责
|
|
|
+
|
|
|
+| 子模块 | 职责 | 对应支付宝API | 优先级 |
|
|
|
+|-------|------|--------------|-------|
|
|
|
+| enterprise | 企业入驻、签约、解约、注销 | alipay.commerce.ec.enterprise.* | P0 |
|
|
|
+| department | 部门创建、修改、删除、查询 | (本地管理,无支付宝API) | P1 |
|
|
|
+| employee | 员工添加、删除、激活、邀请链接 | alipay.commerce.ec.employee.* | P0 |
|
|
|
+| expense_institution | 费控制度CRUD、成员管理 | alipay.ebpp.invoice.institution.* | P0 |
|
|
|
+| expense_rule | 使用规则创建、编辑、删除 | alipay.ebpp.invoice.institution.expenserule.* | P1 |
|
|
|
+| quota | 额度创建、发放、查询、修改 | alipay.ebpp.invoice.expensecontrol.quota.* | P0 |
|
|
|
+| bill | 账单接收、查询、对账 | alipay.commerce.ec.consume.* | P0 |
|
|
|
+| voucher | 凭证存储、查询 | alipay.commerce.ec.voucher.* | P1 |
|
|
|
+| payment | 扫一扫/付款码跳转链接生成 | (本地生成,无支付宝API) | P0 |
|
|
|
+| notification | 支付宝消息接收、处理、分发 | (Webhook回调处理) | P0 |
|
|
|
+
|
|
|
+## 四、数据模型设计
|
|
|
+
|
|
|
+### 4.1 企业模块 (enterprise/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+import enum
|
|
|
+from sqlalchemy import String, DateTime
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin, UserMixin
|
|
|
+
|
|
|
+
|
|
|
+class EnterpriseStatusEnum(enum.Enum):
|
|
|
+ REGISTERED = "registered" # 已注册
|
|
|
+ SIGNED = "signed" # 已签约
|
|
|
+ UNSIGNED = "unsigned" # 已解约
|
|
|
+ DELETED = "deleted" # 已注销
|
|
|
+
|
|
|
+
|
|
|
+class EnterpriseModel(ModelMixin, UserMixin):
|
|
|
+ __tablename__ = "t_enterprise"
|
|
|
+ __table_args__ = {"comment": "企业表"}
|
|
|
+
|
|
|
+ enterprise_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="企业码企业ID")
|
|
|
+ account_id: Mapped[str | None] = mapped_column(String(64), comment="支付宝账号ID")
|
|
|
+ name: Mapped[str] = mapped_column(String(128), comment="企业名称")
|
|
|
+ status: Mapped[str] = mapped_column(String(32), default=EnterpriseStatusEnum.REGISTERED.value, comment="状态")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 部门模块 (department/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+from sqlalchemy import String, ForeignKey
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin
|
|
|
+
|
|
|
+
|
|
|
+class DepartmentModel(ModelMixin):
|
|
|
+ __tablename__ = "t_department"
|
|
|
+ __table_args__ = {"comment": "部门表"}
|
|
|
+
|
|
|
+ department_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="部门ID")
|
|
|
+ enterprise_id: Mapped[str] = mapped_column(String(64), index=True, comment="所属企业ID")
|
|
|
+ name: Mapped[str] = mapped_column(String(128), comment="部门名称")
|
|
|
+ parent_id: Mapped[str | None] = mapped_column(String(64), comment="上级部门ID")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.3 员工模块 (employee/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+import enum
|
|
|
+from sqlalchemy import String, DateTime, ForeignKey
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin, UserMixin
|
|
|
+
|
|
|
+
|
|
|
+class EmployeeStatusEnum(enum.Enum):
|
|
|
+ PENDING = "pending" # 待激活
|
|
|
+ ACTIVE = "active" # 已激活
|
|
|
+ DELETED = "deleted" # 已删除
|
|
|
+
|
|
|
+
|
|
|
+class EmployeeModel(ModelMixin, UserMixin):
|
|
|
+ __tablename__ = "t_employee"
|
|
|
+ __table_args__ = {"comment": "员工表"}
|
|
|
+
|
|
|
+ employee_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="员工ID")
|
|
|
+ enterprise_id: Mapped[str] = mapped_column(String(64), index=True, comment="所属企业ID")
|
|
|
+ alipay_user_id: Mapped[str | None] = mapped_column(String(64), comment="支付宝用户ID")
|
|
|
+ name: Mapped[str] = mapped_column(String(64), comment="员工姓名")
|
|
|
+ phone: Mapped[str | None] = mapped_column(String(32), comment="手机号")
|
|
|
+ email: Mapped[str | None] = mapped_column(String(128), comment="邮箱")
|
|
|
+ department_id: Mapped[str | None] = mapped_column(String(64), comment="部门ID")
|
|
|
+ status: Mapped[str] = mapped_column(String(32), default=EmployeeStatusEnum.PENDING.value, comment="状态")
|
|
|
+ bind_time: Mapped[datetime | None] = mapped_column(DateTime, comment="绑定时间")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.4 费控制度模块 (expense_institution/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+from sqlalchemy import String, DECIMAL, DateTime
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin, UserMixin
|
|
|
+
|
|
|
+
|
|
|
+class ExpenseInstitutionModel(ModelMixin, UserMixin):
|
|
|
+ __tablename__ = "t_expense_institution"
|
|
|
+ __table_args__ = {"comment": "费控制度表"}
|
|
|
+
|
|
|
+ institution_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="制度ID")
|
|
|
+ enterprise_id: Mapped[str] = mapped_column(String(64), index=True, comment="所属企业ID")
|
|
|
+ name: Mapped[str] = mapped_column(String(128), comment="制度名称")
|
|
|
+ expense_type: Mapped[str] = mapped_column(String(32), default="DEFAULT", comment="费用类型")
|
|
|
+ expense_sub_type: Mapped[str] = mapped_column(String(32), default="DEFAULT", comment="费用类型子类")
|
|
|
+ status: Mapped[str] = mapped_column(String(32), default="active", comment="状态")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.5 使用规则模块 (expense_rule/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+from sqlalchemy import String, DECIMAL, DateTime, ForeignKey
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin
|
|
|
+
|
|
|
+
|
|
|
+class ExpenseRuleModel(ModelMixin):
|
|
|
+ __tablename__ = "t_expense_rule"
|
|
|
+ __table_args__ = {"comment": "使用规则表"}
|
|
|
+
|
|
|
+ rule_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="规则ID")
|
|
|
+ institution_id: Mapped[str] = mapped_column(String(64), index=True, comment="所属制度ID")
|
|
|
+ name: Mapped[str | None] = mapped_column(String(128), comment="规则名称")
|
|
|
+ max_amount: Mapped[float | None] = mapped_column(DECIMAL(12,2), comment="最大限额")
|
|
|
+ valid_from: Mapped[datetime | None] = mapped_column(DateTime, comment="有效期开始")
|
|
|
+ valid_to: Mapped[datetime | None] = mapped_column(DateTime, comment="有效期结束")
|
|
|
+ merchant_pid: Mapped[str | None] = mapped_column(String(64), comment="限定商户PID")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.6 额度模块 (quota/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+import enum
|
|
|
+from sqlalchemy import String, DECIMAL, DateTime, ForeignKey
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin, UserMixin
|
|
|
+
|
|
|
+
|
|
|
+class QuotaStatusEnum(enum.Enum):
|
|
|
+ ACTIVE = "active" # 正常
|
|
|
+ FROZEN = "frozen" # 冻结
|
|
|
+ EXHAUSTED = "exhausted" # 已用完
|
|
|
+ EXPIRED = "expired" # 已过期
|
|
|
+
|
|
|
+
|
|
|
+class QuotaModel(ModelMixin, UserMixin):
|
|
|
+ __tablename__ = "t_quota"
|
|
|
+ __table_args__ = {"comment": "额度表"}
|
|
|
+
|
|
|
+ quota_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="额度ID")
|
|
|
+ employee_id: Mapped[str] = mapped_column(String(64), index=True, comment="员工ID")
|
|
|
+ institution_id: Mapped[str] = mapped_column(String(64), index=True, comment="制度ID")
|
|
|
+ outer_source_id: Mapped[str] = mapped_column(String(64), unique=True, comment="外部来源ID(幂等)")
|
|
|
+ total_amount: Mapped[float] = mapped_column(DECIMAL(12,2), comment="总金额")
|
|
|
+ available_amount: Mapped[float] = mapped_column(DECIMAL(12,2), comment="可用金额")
|
|
|
+ valid_from: Mapped[datetime] = mapped_column(DateTime, comment="有效期开始")
|
|
|
+ valid_to: Mapped[datetime] = mapped_column(DateTime, comment="有效期结束")
|
|
|
+ status: Mapped[str] = mapped_column(String(32), default=QuotaStatusEnum.ACTIVE.value, comment="状态")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.7 账单模块 (bill/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+import enum
|
|
|
+from sqlalchemy import String, DECIMAL, DateTime, Text
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin
|
|
|
+
|
|
|
+
|
|
|
+class BillStatusEnum(enum.Enum):
|
|
|
+ PAID = "paid" # 已支付
|
|
|
+ REFUNDED = "refunded" # 已退款
|
|
|
+ CLOSED = "closed" # 已关闭
|
|
|
+
|
|
|
+
|
|
|
+class BillModel(ModelMixin):
|
|
|
+ __tablename__ = "t_bill"
|
|
|
+ __table_args__ = {"comment": "账单表"}
|
|
|
+
|
|
|
+ bill_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="账单ID")
|
|
|
+ trade_no: Mapped[str] = mapped_column(String(64), index=True, comment="支付宝交易号")
|
|
|
+ enterprise_id: Mapped[str] = mapped_column(String(64), index=True, comment="企业ID")
|
|
|
+ employee_id: Mapped[str] = mapped_column(String(64), index=True, comment="员工ID")
|
|
|
+ institution_id: Mapped[str | None] = mapped_column(String(64), comment="制度ID")
|
|
|
+ quota_id: Mapped[str | None] = mapped_column(String(64), comment="额度ID")
|
|
|
+ total_amount: Mapped[float] = mapped_column(DECIMAL(12,2), comment="账单金额")
|
|
|
+ enterprise_pay_amount: Mapped[float] = mapped_column(DECIMAL(12,2), default=0, comment="企业付金额")
|
|
|
+ personal_pay_amount: Mapped[float] = mapped_column(DECIMAL(12,2), default=0, comment="个人付金额")
|
|
|
+ discount_amount: Mapped[float] = mapped_column(DECIMAL(12,2), default=0, comment="优惠金额")
|
|
|
+ trade_time: Mapped[datetime | None] = mapped_column(DateTime, comment="交易时间")
|
|
|
+ merchant_name: Mapped[str | None] = mapped_column(String(256), comment="商户名称")
|
|
|
+ status: Mapped[str] = mapped_column(String(32), default=BillStatusEnum.PAID.value, comment="状态")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.8 凭证模块 (voucher/model.py)
|
|
|
+
|
|
|
+```python
|
|
|
+from sqlalchemy import String, JSON, ForeignKey
|
|
|
+from sqlalchemy.orm import Mapped, mapped_column
|
|
|
+
|
|
|
+from app.core.base_model import ModelMixin
|
|
|
+
|
|
|
+
|
|
|
+class VoucherModel(ModelMixin):
|
|
|
+ __tablename__ = "t_voucher"
|
|
|
+ __table_args__ = {"comment": "凭证表"}
|
|
|
+
|
|
|
+ voucher_id: Mapped[str] = mapped_column(String(64), unique=True, index=True, comment="凭证ID")
|
|
|
+ bill_id: Mapped[str] = mapped_column(String(64), index=True, comment="关联账单ID")
|
|
|
+ content: Mapped[dict | None] = mapped_column(JSON, comment="凭证内容")
|
|
|
+```
|
|
|
+
|
|
|
+### 4.9 消息通知模块 (notification/handlers)
|
|
|
+
|
|
|
+通知处理器采用策略模式,根据消息类型分发到不同的Handler处理:
|
|
|
+
|
|
|
+```python
|
|
|
+# notification/handlers/__init__.py
|
|
|
+from notification.handlers.enterprise_handler import EnterpriseChangeHandler
|
|
|
+from notification.handlers.employee_handler import EmployeeChangeHandler
|
|
|
+from notification.handlers.bill_handler import BillChangeHandler
|
|
|
+from notification.handlers.voucher_handler import VoucherChangeHandler
|
|
|
+
|
|
|
+HANDLER_MAP = {
|
|
|
+ "alipay.commerce.ec.enterprise.change.notify": EnterpriseChangeHandler,
|
|
|
+ "alipay.commerce.ec.employee.change.notify": EmployeeChangeHandler,
|
|
|
+ "alipay.commerce.ec.consume.change.notify": BillChangeHandler,
|
|
|
+ "alipay.commerce.ec.voucher.change.notify": VoucherChangeHandler,
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 五、服务层设计
|
|
|
+
|
|
|
+### 5.1 企业签约服务 (enterprise/service.py)
|
|
|
+
|
|
|
+```python
|
|
|
+from typing import TYPE_CHECKING
|
|
|
+from alipaySdk import AlipaySDK
|
|
|
+
|
|
|
+if TYPE_CHECKING:
|
|
|
+ from enterprise.model import EnterpriseModel
|
|
|
+
|
|
|
+
|
|
|
+class EnterpriseService:
|
|
|
+
|
|
|
+ def __init__(self, alipay_sdk: AlipaySDK):
|
|
|
+ self._sdk = alipay_sdk
|
|
|
+
|
|
|
+ async def create_invite_url(self, enterprise: "EnterpriseModel") -> str:
|
|
|
+ """生成企业注册邀请链接"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.commerce.ec.enterprise.registerinvite.create",
|
|
|
+ {"enterprise_name": enterprise.name}
|
|
|
+ )
|
|
|
+ return result.get("invite_url")
|
|
|
+
|
|
|
+ async def handle_status_change(self, notify_data: dict) -> None:
|
|
|
+ """处理企业状态变更通知"""
|
|
|
+ # 更新企业状态
|
|
|
+ pass
|
|
|
+```
|
|
|
+
|
|
|
+### 5.2 员工服务 (employee/service.py)
|
|
|
+
|
|
|
+```python
|
|
|
+class EmployeeService:
|
|
|
+
|
|
|
+ async def add_employee(self, enterprise_id: str, name: str, phone: str) -> dict:
|
|
|
+ """添加企业员工"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.commerce.ec.employee.add",
|
|
|
+ {
|
|
|
+ "enterprise_id": enterprise_id,
|
|
|
+ "name": name,
|
|
|
+ "phone": phone,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ return result
|
|
|
+
|
|
|
+ async def delete_employee(self, employee_id: str) -> dict:
|
|
|
+ """删除企业员工"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.commerce.ec.employee.delete",
|
|
|
+ {"employee_id": employee_id}
|
|
|
+ )
|
|
|
+ return result
|
|
|
+
|
|
|
+ async def get_invite_url(self, employee_id: str) -> str:
|
|
|
+ """获取员工邀请链接"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.commerce.ec.employee.invite.query",
|
|
|
+ {"employee_id": employee_id}
|
|
|
+ )
|
|
|
+ return result.get("sign_url")
|
|
|
+```
|
|
|
+
|
|
|
+### 5.3 费控服务 (expense_institution/service.py)
|
|
|
+
|
|
|
+```python
|
|
|
+class ExpenseInstitutionService:
|
|
|
+
|
|
|
+ async def create_institution(self, enterprise_id: str, name: str, **kwargs) -> dict:
|
|
|
+ """创建费控制度"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.ebpp.invoice.institution.create",
|
|
|
+ {
|
|
|
+ "enterprise_id": enterprise_id,
|
|
|
+ "name": name,
|
|
|
+ "expense_type": kwargs.get("expense_type", "DEFAULT"),
|
|
|
+ "expense_sub_type": kwargs.get("expense_sub_type", "DEFAULT"),
|
|
|
+ }
|
|
|
+ )
|
|
|
+ return result
|
|
|
+
|
|
|
+ async def modify_institution(self, institution_id: str, **kwargs) -> dict:
|
|
|
+ """编辑费控制度"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.ebpp.invoice.institution.modify",
|
|
|
+ {"institution_id": institution_id, **kwargs}
|
|
|
+ )
|
|
|
+ return result
|
|
|
+```
|
|
|
+
|
|
|
+### 5.4 额度服务 (quota/service.py)
|
|
|
+
|
|
|
+```python
|
|
|
+class QuotaService:
|
|
|
+
|
|
|
+ async def create_quota(self, employee_id: str, institution_id: str,
|
|
|
+ total_amount: float, valid_from: datetime,
|
|
|
+ valid_to: datetime, outer_source_id: str) -> dict:
|
|
|
+ """创建手工发放额度"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.ebpp.invoice.expensecontrol.quota.create",
|
|
|
+ {
|
|
|
+ "target_type": "INSTITUTION",
|
|
|
+ "target_id": institution_id,
|
|
|
+ "employee_id": employee_id,
|
|
|
+ "amount": total_amount,
|
|
|
+ "valid_from": valid_from.isoformat(),
|
|
|
+ "valid_to": valid_to.isoformat(),
|
|
|
+ "outer_source_id": outer_source_id,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ return result
|
|
|
+
|
|
|
+ async def query_quota(self, employee_id: str, institution_id: str) -> dict:
|
|
|
+ """查询员工可用额度"""
|
|
|
+ result = await self._sdk.execute(
|
|
|
+ "alipay.ebpp.invoice.expensecontrol.quota.query",
|
|
|
+ {
|
|
|
+ "employee_id": employee_id,
|
|
|
+ "target_type": "INSTITUTION",
|
|
|
+ "target_id": institution_id,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ return result
|
|
|
+```
|
|
|
+
|
|
|
+### 5.5 支付跳转服务 (payment/service.py)
|
|
|
+
|
|
|
+```python
|
|
|
+import json
|
|
|
+from urllib.parse import quote
|
|
|
+
|
|
|
+
|
|
|
+class PaymentService:
|
|
|
+
|
|
|
+ def generate_scan_params(self, account_id: str, rule_group_id: str,
|
|
|
+ payment_id: str, isv_app_id: str) -> dict:
|
|
|
+ """生成扫一扫跳转参数"""
|
|
|
+ params = {
|
|
|
+ "pdSubBizScene": "enterprisePay",
|
|
|
+ "specifiedEnableChannelInfo": json.dumps({
|
|
|
+ "enableScene": "agreementpay",
|
|
|
+ "assetInfo": {
|
|
|
+ "instId": "INST_ALIPAY",
|
|
|
+ "assetId": account_id,
|
|
|
+ "assetTypeCode": "ENTERPRISEPAY",
|
|
|
+ "assetType": "ENTERPRISEPAYASSET"
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ "assignJointAccountId": account_id,
|
|
|
+ "identityPayBizInfo": json.dumps({
|
|
|
+ "identityPaySubBizScene": "ISV_PAY",
|
|
|
+ "bizGroupId": account_id,
|
|
|
+ "groupId": account_id,
|
|
|
+ "identityPayBizScene": "ENTERPRISE_CODE"
|
|
|
+ }),
|
|
|
+ "CHANNEL_INDEX": "[\"ENTERPRISEPAYASSET_DC_ENTERPRISEPAY_DEFAULT\"]",
|
|
|
+ "enterprise_pay_info": json.dumps({
|
|
|
+ "paymentId": payment_id,
|
|
|
+ "ruleGroupId": rule_group_id
|
|
|
+ }),
|
|
|
+ "sourcePlatformInfo": json.dumps({
|
|
|
+ "paymentId": payment_id,
|
|
|
+ "ruleGroupId": rule_group_id,
|
|
|
+ "isvAppId": isv_app_id
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return params
|
|
|
+
|
|
|
+ def generate_payment_code_url(self, account_id: str, rule_group_id: str,
|
|
|
+ payment_id: str, isv_app_id: str) -> str:
|
|
|
+ """生成付款码跳转链接"""
|
|
|
+ params = self.generate_scan_params(account_id, rule_group_id, payment_id, isv_app_id)
|
|
|
+ params["channelMode"] = "NONE_CHANNEL_MODE"
|
|
|
+
|
|
|
+ encoded_params = quote(json.dumps(params), safe='')
|
|
|
+ return f"alipays://platformapi/startapp?appId=20000056&customBizCode=enterprisePayForThirdPart&customBizParams={encoded_params}"
|
|
|
+```
|
|
|
+
|
|
|
+## 六、API路由设计
|
|
|
+
|
|
|
+### 6.1 企业管理路由 (enterprise/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/payment/enterprise/invite # 创建企业邀请
|
|
|
+GET /api/payment/enterprise/{enterprise_id} # 查询企业详情
|
|
|
+PUT /api/payment/enterprise/{enterprise_id} # 修改企业信息
|
|
|
+POST /api/payment/enterprise/{enterprise_id}/unsign # 企业解约
|
|
|
+POST /api/payment/enterprise/{enterprise_id}/delete # 企业注销
|
|
|
+```
|
|
|
+
|
|
|
+### 6.2 员工管理路由 (employee/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/payment/employee # 添加员工
|
|
|
+DELETE /api/payment/employee/{employee_id} # 删除员工
|
|
|
+GET /api/payment/employee/{employee_id} # 查询员工详情
|
|
|
+PUT /api/payment/employee/{employee_id} # 修改员工信息
|
|
|
+GET /api/payment/employee/list # 查询员工列表
|
|
|
+GET /api/payment/employee/{employee_id}/invite-url # 获取邀请链接
|
|
|
+```
|
|
|
+
|
|
|
+### 6.3 费控管理路由 (expense_institution/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/payment/institution # 创建费控制度
|
|
|
+PUT /api/payment/institution/{institution_id} # 编辑费控制度
|
|
|
+GET /api/payment/institution/{institution_id} # 查询制度详情
|
|
|
+GET /api/payment/institution/list # 查询制度列表
|
|
|
+DELETE /api/payment/institution/{institution_id} # 删除费控制度
|
|
|
+POST /api/payment/institution/{institution_id}/members # 编辑制度成员
|
|
|
+```
|
|
|
+
|
|
|
+### 6.4 额度管理路由 (quota/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/payment/quota # 创建额度
|
|
|
+GET /api/payment/quota/{quota_id} # 查询额度详情
|
|
|
+GET /api/payment/quota/employee/{employee_id} # 查询员工可用额度
|
|
|
+PUT /api/payment/quota/{quota_id} # 修改额度
|
|
|
+DELETE /api/payment/quota/{quota_id} # 删除额度
|
|
|
+```
|
|
|
+
|
|
|
+### 6.5 账单管理路由 (bill/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+GET /api/payment/bill/{bill_id} # 查询账单详情
|
|
|
+GET /api/payment/bill/list # 查询账单列表
|
|
|
+GET /api/payment/bill/download # 下载对账单
|
|
|
+```
|
|
|
+
|
|
|
+### 6.6 支付跳转路由 (payment/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/payment/pay/scan-url # 生成扫一扫跳转链接
|
|
|
+POST /api/payment/pay/code-url # 生成付款码跳转链接
|
|
|
+POST /api/payment/pay/invoice-url # 生成发票关联跳转链接
|
|
|
+```
|
|
|
+
|
|
|
+### 6.7 消息通知路由 (notification/controller.py)
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/payment/notification/enterprise # 企业状态变更回调
|
|
|
+POST /api/payment/notification/employee # 员工变更回调
|
|
|
+POST /api/payment/notification/bill # 账单变动回调
|
|
|
+POST /api/payment/notification/voucher # 凭证变动回调
|
|
|
+WS /ws/payment/notification # WebSocket长连接
|
|
|
+```
|
|
|
+
|
|
|
+## 七、依赖关系
|
|
|
+
|
|
|
+```
|
|
|
+┌─────────────────────────────────────────────────────────────────┐
|
|
|
+│ API Controller Layer │
|
|
|
+│ enterprise | employee | expense_institution | quota | bill │
|
|
|
+│ payment | notification │
|
|
|
+└─────────────────────────────────────────────────────────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+┌─────────────────────────────────────────────────────────────────┐
|
|
|
+│ Service Layer │
|
|
|
+│ EnterpriseService | EmployeeService | ExpenseService | ... │
|
|
|
+└─────────────────────────────────────────────────────────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+┌─────────────────────────────────────────────────────────────────┐
|
|
|
+│ Alipay SDK Layer │
|
|
|
+│ (统一封装支付宝API调用) │
|
|
|
+└─────────────────────────────────────────────────────────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+┌─────────────────────────────────────────────────────────────────┐
|
|
|
+│ CRUD Layer │
|
|
|
+│ Model | Schema | CRUD (与数据库交互) │
|
|
|
+└─────────────────────────────────────────────────────────────────┘
|
|
|
+ │
|
|
|
+ ▼
|
|
|
+┌─────────────────────────────────────────────────────────────────┐
|
|
|
+│ PostgreSQL Database │
|
|
|
+└─────────────────────────────────────────────────────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+## 八、实施计划
|
|
|
+
|
|
|
+### 阶段一:基础框架(1周)
|
|
|
+
|
|
|
+1. 创建 `module_payment` 目录结构
|
|
|
+2. 定义 `plugin.toml` 元数据
|
|
|
+3. 创建各子模块的基础文件骨架
|
|
|
+4. 配置数据库连接和ORM模型
|
|
|
+
|
|
|
+### 阶段二:核心模块开发(3-4周)
|
|
|
+
|
|
|
+| 周次 | 开发内容 |
|
|
|
+|-----|---------|
|
|
|
+| 第2周 | enterprise + department 模块 |
|
|
|
+| 第3周 | employee + notification 基础 |
|
|
|
+| 第4周 | expense_institution + expense_rule |
|
|
|
+| 第5周 | quota + payment |
|
|
|
+
|
|
|
+### 阶段三:账单和凭证模块(1-2周)
|
|
|
+
|
|
|
+1. bill 模块开发
|
|
|
+2. voucher 模块开发
|
|
|
+3. 消息通知处理器完善
|
|
|
+
|
|
|
+### 阶段四:集成测试(1周)
|
|
|
+
|
|
|
+1. 模块间依赖测试
|
|
|
+2. 支付宝API联调
|
|
|
+3. 整体业务流程测试
|
|
|
+
|
|
|
+## 九、注意事项
|
|
|
+
|
|
|
+1. **命名规范**:遵循 `module_{一级模块}_{二级模块}` 的目录命名约定
|
|
|
+2. **ModelMixin**:所有模型类需继承 `ModelMixin`,支持通用CRUD
|
|
|
+3. **幂等性**:额度等关键操作需使用 `outer_source_id` 保证幂等
|
|
|
+4. **错误处理**:支付宝API调用需统一异常处理和日志记录
|
|
|
+5. **事务管理**:跨表操作需使用数据库事务保证一致性
|