import json from datetime import datetime from fastapi import Request from fastapi.datastructures import FormData from pydantic import BaseModel, Field, model_validator from app.core.logger import log async def parse_alipay_notify_form(request: Request) -> "AlipayNotifyBase": """解析支付宝通知表单数据""" form_data = await request.form() # 确保所有值都是字符串类型 return parse_alipay_notify_form_data(form_data) def parse_alipay_notify_form_data(form_data: FormData) -> "AlipayNotifyBase": """解析支付宝通知表单数据""" form_dict = {} for key, value in form_data.items(): if isinstance(value, str): form_dict[key] = value else: # 对于非字符串类型(如UploadFile),获取其字符串表示 form_dict[key] = str(value) log.info(f"收到支付宝通知表单数据: {form_dict}") return AlipayNotifyBase(**form_dict) class AlipayNotifyBase(BaseModel): """支付宝通知基础模型""" notify_id: str = Field(..., description="通知ID") utc_timestamp: str = Field(..., description="消息发送时的服务端时间") msg_method: str | None = Field(None, description="消息接口名称(优先取此值,若为空则用 notify_type)") app_id: str = Field(..., description="消息接受方的应用id") version: str = Field(..., description="版本号") sign: str = Field(..., description="签名") sign_type: str = Field(..., description="签名类型") charset: str = Field(..., description="编码集") notify_type: str | None = Field(None, description="通知类型(msg_method 为空时使用)") biz_content: str = Field(..., description="消息报文") def parse_biz_content(self) -> dict: """解析 biz_content JSON 字符串""" try: return json.loads(self.biz_content) except: raise Exception(f"支付宝通知消息 - 解析 biz_content 失败") @property def method(self) -> str: """获取消息接口标识,优先 msg_method,其次 notify_type""" return self.msg_method or self.notify_type or "" class EnterpriseChangeContent(BaseModel): """企业变更通知内容""" enterprise_id: str = Field(..., description="企业id") out_biz_no: str = Field(..., description="外部业务号/幂等号") action: str = Field(..., description="变更动作") change_time: str = Field(..., description="变更时间") account_id: str | None = Field(None, description="账户id") remark: str | None = Field(None, description="备注") tenant_id: int | None = Field(None, description="租户id") class EmployeeChangeContent(BaseModel): """ 员工变更通知内容 {'gmt_create': '2026-04-24 11:15:49', 'role_list': ['USER'], 'mobile': '13874410370', 'employee_name': '胡森林', 'gmt_modified': '2026-04-24 11:15:49', 'ext_info': '{}', 'enterprise_id': '2088480767913636', 'iot_face_status': '2', 'department_list': [{'department_id': '1001163032541324', 'department_name': '湖南花米惠科技有限公司'}], 'employee_id': '2300063240699375', 'activate': 'UNACTIVATED', 'action': 'EMPLOYEE_ADD', 'change_time': '2026-04-24 11:15:49'} """ enterprise_id: str = Field(..., description="企业id") employee_id: str = Field(..., description="员工id") action: str | None = Field(None, description="变更动作") change_time: str | None = Field(None, description="变更时间") activate: str | None = Field(None, description="激活状态") department_list: list | None = Field(None, description="部门列表") role_list: list | None = Field(None, description="角色列表") mobile: str | None = Field(None, description="手机号") email: str | None = Field(None, description="邮箱") open_id: str | None = Field(None, description="open_id") employee_name: str | None = Field(None, description="员工姓名") iot_face_status: str | None = Field(None, description="人脸状态状态") class VoucherChangeContent(BaseModel): """凭证变动通知内容""" account_id: str = Field(..., description="账户id") voucher_id: str = Field(..., description="凭证id") enterprise_id: str = Field(..., description="企业id") out_biz_no: str = Field(..., description="外部业务号") action: str = Field(..., description="变动动作") change_time: str = Field(..., description="变动时间") class FundChangeContent(BaseModel): """资金变动通知内容""" enterprise_id: str = Field(..., description="企业id") account_book_id: str = Field(..., description="资金专户号") out_biz_no: str = Field(..., description="外部业务号") change_type: str = Field(..., description="变动类型: DEPOSIT/WITHDRAW/TRANSFER") status: str = Field(..., description="状态") amount: str | None = Field(None, description="金额") order_no: str | None = Field(None, description="支付宝订单号") error_code: str | None = Field(None, description="错误码") error_message: str | None = Field(None, description="错误信息") change_time: str = Field(..., description="变动时间") class ConsumeChangeContent(BaseModel): """企业码账单消息通知内容""" # 必选字段 account_id: str = Field(..., description="共同账户ID") enterprise_id: str = Field(..., description="企业ID") employee_id: str = Field(..., description="员工ID") pay_no: str = Field(..., description="支付宝账单号") consume_type: str = Field(..., description="账单类型: CONSUME(消费账单)/REFUND(退款账单)") gmt_biz_create: str = Field(..., description="账单创建时间,格式:yyyy-MM-dd HH:mm:ss") consume_amount: str = Field(..., description="账单金额,2位小数,单位:元") notify_reason: str = Field(..., description="通知原因") notify_msg: str = Field(..., description="通知描述") # 二选一字段 user_id: str | None = Field(None, description="员工支付宝UID") open_id: str | None = Field(None, description="员工open_id") # 条件必选字段(退款账单才有值) related_pay_no: str | None = Field(None, description="关联消费账单交易流水号") # 可选字段 gmt_recieve_pay: str | None = Field(None, description="账单支付时间,格式:yyyy-MM-dd HH:mm:ss") peer_pay_amount: str | None = Field(None, description="企业代付金额,2位小数,单位:元") expense_rule_group_id: str | None = Field(None, description="费控规则ID") expense_scene_code: str | None = Field(None, description="费用场景") expense_type: str | None = Field(None, description="费用类型") expense_type_sub_category: str | None = Field(None, description="费用类型子类目") ext_infos: str | None = Field(None, description="扩展参数,JSON格式") class OrderChangeContent(BaseModel): """订单变动通知内容 接口名: alipay.ebpp.invoice.ecorder.order.changed 参考文档: https://opendocs.alipay.com/apis/03iu5y """ order_no: str = Field(..., description="支付宝订单号") biz_out_no: str = Field(..., description="商户订单号") enterprise_id: str = Field(..., description="企业ID") employee_id: str | None = Field(None, description="员工ID") out_biz_no: str | None = Field(None, description="外部业务号") action: str | None = Field(None, description="变动动作") change_time: str | None = Field(None, description="变动时间") order_status: str | None = Field(None, description="订单状态") # 补充字段 pay_no: str | None = Field(None, description="账单号") order_title: str | None = Field(None, description="订单标题") order_amount: str | None = Field(None, description="订单金额") trade_no: str | None = Field(None, description="交易号") merchant_id: str | None = Field(None, description="商户ID") merchant_name: str | None = Field(None, description="商户名称") shop_id: str | None = Field(None, description="店铺ID") shop_name: str | None = Field(None, description="店铺名称") gmt_create: str | None = Field(None, description="创建时间") gmt_payment: str | None = Field(None, description="支付时间") fund_channel: str | None = Field(None, description="资金渠道") order_type: str | None = Field(None, description="订单类型") invoice_status: str | None = Field(None, description="发票状态") ext_info: str | None = Field(None, description="扩展信息") # 把order_no设置为pay_no的值 @model_validator(mode="before") def set_order_no(cls, values): if not values.get("order_no") and values.get("pay_no"): values["order_no"] = values["pay_no"] return values class BillDetailQueryOut(BaseModel): """账单详情查询响应""" pay_no: str = Field(..., description="支付宝账单号") enterprise_id: str = Field(..., description="企业ID") employee_id: str = Field(..., description="员工ID") employee_name: str | None = Field(None, description="员工姓名") consume_amount: str = Field(..., description="账单金额") peer_pay_amount: str | None = Field(None, description="企业代付金额") employee_pay_amount: str | None = Field(None, description="员工自付金额") order_info: dict | None = Field(None, description="订单详情") voucher_list: list[dict] | None = Field(None, description="凭证列表")