controller.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import json
  2. from typing import Annotated
  3. from apscheduler.schedulers.asyncio import AsyncIOScheduler
  4. from fastapi import APIRouter, Depends, Form, Path, Query, UploadFile
  5. from fastapi.responses import JSONResponse
  6. from app.api.v1.module_system.auth.schema import AuthSchema
  7. from app.common.response import ResponseSchema, SuccessResponse
  8. from app.core.dependencies import AuthPermission
  9. from app.core.logger import log
  10. from app.core.router_class import OperationLogRoute
  11. from .schema import (
  12. FacetofaceApplySchema,
  13. FacetofaceOrderListOutSchema,
  14. FacetofaceOrderOutSchema,
  15. )
  16. from .service import FacetofaceService
  17. FacetofaceRouter = APIRouter(
  18. route_class=OperationLogRoute,
  19. prefix="/facetoface",
  20. tags=["当面付开通"],
  21. )
  22. @FacetofaceRouter.post(
  23. "/apply",
  24. summary="提交当面付开通申请",
  25. description="代商家提交当面付开通申请(自动完成 create → sign → confirm 三步),支持图片上传",
  26. response_model=ResponseSchema[FacetofaceOrderOutSchema],
  27. )
  28. async def apply_controller(
  29. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:apply"]))],
  30. data: Annotated[str, Form(description="申请数据 JSON")],
  31. shop_scene_pic: Annotated[UploadFile | None, Form()] = None,
  32. shop_sign_board_pic: Annotated[UploadFile | None, Form()] = None,
  33. business_license_pic: Annotated[UploadFile | None, Form()] = None,
  34. ) -> JSONResponse:
  35. apply_data = FacetofaceApplySchema.model_validate_json(data)
  36. result = await FacetofaceService.apply_service(
  37. auth=auth,
  38. data=apply_data,
  39. shop_scene_pic=shop_scene_pic,
  40. shop_sign_board_pic=shop_sign_board_pic,
  41. business_license_pic=business_license_pic,
  42. )
  43. log.info(f"当面付开通申请已提交: batch_no={result.batch_no}, merchant={apply_data.merchant_name}")
  44. return SuccessResponse(data=result, msg="当面付开通申请已提交")
  45. @FacetofaceRouter.get(
  46. "",
  47. summary="查询申请单列表",
  48. description="分页查询当面付开通申请单列表",
  49. response_model=ResponseSchema[FacetofaceOrderListOutSchema],
  50. )
  51. async def list_controller(
  52. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:list"]))],
  53. page_no: Annotated[int, Query(description="页码")] = 1,
  54. page_size: Annotated[int, Query(description="每页数量")] = 20,
  55. merchant_name: Annotated[str | None, Query(description="商户名称")] = None,
  56. shop_name: Annotated[str | None, Query(description="店铺名称")] = None,
  57. order_status: Annotated[str | None, Query(description="申请单状态")] = None,
  58. start_time: Annotated[str | None, Query(description="开始时间")] = None,
  59. end_time: Annotated[str | None, Query(description="结束时间")] = None,
  60. ) -> JSONResponse:
  61. search = {}
  62. if merchant_name:
  63. search["merchant_name"] = merchant_name
  64. if shop_name:
  65. search["shop_name"] = shop_name
  66. if order_status:
  67. search["order_status"] = order_status
  68. if start_time:
  69. search["start_time"] = start_time
  70. if end_time:
  71. search["end_time"] = end_time
  72. result = await FacetofaceService.list_service(
  73. auth=auth, page_no=page_no, page_size=page_size, search=search
  74. )
  75. return SuccessResponse(data=result, msg="查询成功")
  76. @FacetofaceRouter.get(
  77. "/enterprises/status",
  78. summary="批量查询企业当面付状态",
  79. description="传入企业ID列表,返回各企业的当面付申请状态",
  80. )
  81. async def batch_status_controller(
  82. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:enterprise:list"]))],
  83. ids: Annotated[str, Query(description="企业ID列表,逗号分隔")],
  84. ) -> JSONResponse:
  85. enterprise_ids = [eid.strip() for eid in ids.split(",") if eid.strip()]
  86. result = await FacetofaceService.batch_status_service(auth=auth, enterprise_ids=enterprise_ids)
  87. return SuccessResponse(data=result, msg="查询成功")
  88. @FacetofaceRouter.get(
  89. "/enterprise/{enterprise_id}",
  90. summary="按企业ID查询当面付申请单",
  91. description="查询某个企业的当面付开通申请单",
  92. response_model=ResponseSchema[FacetofaceOrderOutSchema],
  93. )
  94. async def get_by_enterprise_controller(
  95. enterprise_id: Annotated[str, Path(description="企业ID")],
  96. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:list"]))],
  97. ) -> JSONResponse:
  98. result = await FacetofaceService.get_by_enterprise_service(auth=auth, enterprise_id=enterprise_id)
  99. return SuccessResponse(data=result, msg="查询成功")
  100. @FacetofaceRouter.get(
  101. "/{order_id}",
  102. summary="查询申请单详情",
  103. description="查询单个当面付开通申请单详情",
  104. response_model=ResponseSchema[FacetofaceOrderOutSchema],
  105. )
  106. async def detail_controller(
  107. order_id: Annotated[int, Path(description="申请单ID")],
  108. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:list"]))],
  109. ) -> JSONResponse:
  110. result = await FacetofaceService.detail_service(auth=auth, order_id=order_id)
  111. return SuccessResponse(data=result, msg="查询成功")
  112. @FacetofaceRouter.post(
  113. "/{order_id}/query",
  114. summary="手动查询申请单状态",
  115. description="手动触发查询支付宝申请单最新状态",
  116. response_model=ResponseSchema[FacetofaceOrderOutSchema],
  117. )
  118. async def query_status_controller(
  119. order_id: Annotated[int, Path(description="申请单ID")],
  120. auth: Annotated[AuthSchema, Depends(AuthPermission(["module_payment:facetoface:query"]))],
  121. ) -> JSONResponse:
  122. result = await FacetofaceService.query_order_service(auth=auth, order_id=order_id)
  123. log.info(f"手动查询当面付申请单状态: id={order_id}, status={result.order_status}")
  124. return SuccessResponse(data=result, msg="查询成功")
  125. # ---------- 定时轮询任务 ----------
  126. _poll_scheduler = AsyncIOScheduler()
  127. _poll_scheduler_started = False
  128. @FacetofaceRouter.on_event("startup")
  129. async def start_facetoface_poll_scheduler():
  130. """启动时注册当面付申请单状态轮询任务"""
  131. global _poll_scheduler_started
  132. if _poll_scheduler_started:
  133. return
  134. _poll_scheduler_started = True
  135. _poll_scheduler.add_job(
  136. FacetofaceService.poll_pending_orders,
  137. "interval",
  138. minutes=30,
  139. max_instances=1,
  140. id="facetoface_poll_orders",
  141. replace_existing=True,
  142. )
  143. _poll_scheduler.start()
  144. log.info("[当面付] 状态轮询定时任务已注册(每30分钟执行)")
  145. @FacetofaceRouter.on_event("shutdown")
  146. async def stop_facetoface_poll_scheduler():
  147. if _poll_scheduler.running:
  148. _poll_scheduler.shutdown(wait=False)
  149. log.info("[当面付] 状态轮询定时任务已停止")