service.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. from datetime import datetime
  2. from typing import Optional, cast
  3. from redis.asyncio import Redis
  4. from app.api.v1.module_system.auth.schema import AuthSchema
  5. from app.core.alipay import AlipayClient
  6. from app.core.exceptions import CustomException
  7. from app.core.logger import log
  8. from app.utils.snowflake import get_snowflake_id_str
  9. from .crud import EnterpriseCRUD
  10. from .enums import EnterpriseStatusEnum
  11. from .schema import (
  12. EnterpriseApplyInviteSchema,
  13. EnterpriseCreateOrUpdateSchema,
  14. EnterpriseInviteOutSchema,
  15. EnterpriseListOutSchema,
  16. EnterpriseOperationOutSchema,
  17. )
  18. from alipay.aop.api.domain.EnterpriseInfoDTO import EnterpriseInfoDTO
  19. class EnterpriseService:
  20. """企业服务层"""
  21. @classmethod
  22. async def create_service_from_alipay(
  23. cls, auth: AuthSchema, data: EnterpriseCreateOrUpdateSchema
  24. ) -> EnterpriseOperationOutSchema:
  25. """
  26. 创建企业记录(仅创建记录,不申请邀请码)
  27. """
  28. crud = EnterpriseCRUD(auth)
  29. data.validate_enterprise_id()
  30. enterprise = await crud.create(data.model_dump(exclude_none=True))
  31. if not enterprise:
  32. raise CustomException(msg="创建企业记录失败")
  33. return EnterpriseOperationOutSchema(enterprise_id=enterprise.enterprise_id)
  34. @classmethod
  35. async def apply_invite_service(
  36. cls, auth: AuthSchema, redis: Redis, data: EnterpriseApplyInviteSchema
  37. ) -> EnterpriseInviteOutSchema:
  38. """
  39. 申请企业邀请码(可多次调用,但需检查是否已存在且未过期)
  40. 调用: alipay.commerce.ec.enterprise.registerinvite.create
  41. """
  42. from alipay.aop.api.request.AlipayCommerceEcEnterpriseRegisterinviteCreateRequest import (
  43. AlipayCommerceEcEnterpriseRegisterinviteCreateRequest,
  44. )
  45. from alipay.aop.api.domain.AlipayCommerceEcEnterpriseRegisterinviteCreateModel import (
  46. AlipayCommerceEcEnterpriseRegisterinviteCreateModel, EnterpriseBaseInfoDTO, EnterpriseProfilesDTO
  47. )
  48. from alipay.aop.api.response.AlipayCommerceEcEnterpriseRegisterinviteCreateResponse import (
  49. AlipayCommerceEcEnterpriseRegisterinviteCreateResponse
  50. )
  51. model = AlipayCommerceEcEnterpriseRegisterinviteCreateModel()
  52. model.out_biz_no = get_snowflake_id_str(auth.tenant_id) # 支付宝接口需要 out_biz_no,用业务主键 enterprise_id
  53. model.identity_type = data.identity_type
  54. model.identity = data.identity
  55. model.identity_open_id = data.identity_open_id
  56. # model.register_mode = None
  57. # model.sign_fund_way = None
  58. request = AlipayCommerceEcEnterpriseRegisterinviteCreateRequest()
  59. request.biz_model = model
  60. client = AlipayClient.get_client()
  61. response = client.execute(request)
  62. if not response:
  63. raise CustomException(msg="申请邀请码失败: 无响应")
  64. result = AlipayCommerceEcEnterpriseRegisterinviteCreateResponse()
  65. result.parse_response_content(response)
  66. if not result.is_success():
  67. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  68. raise CustomException(msg=f"申请邀请码失败: {result.msg}")
  69. invite_time = datetime.now()
  70. invite_out = EnterpriseInviteOutSchema(
  71. pc_invite_url=result.pc_invite_url or "",
  72. invite_time=invite_time,
  73. expire_time=result.expire_time,
  74. )
  75. # 根据out_biz_no关联tenant_id存储redis,方便后期支付宝回调用于创建企业, 过期时间为三个月
  76. # await redis.set(f"alipay:notify:{model.out_biz_no}", auth.tenant_id, ex=60 * 5 * 24 * 30)
  77. return invite_out
  78. @classmethod
  79. async def detail_service(cls, auth: AuthSchema, enterprise_id: str) -> EnterpriseInfoDTO:
  80. """
  81. 查询企业详情
  82. 调用: alipay.commerce.ec.enterprise.info.query
  83. """
  84. from alipay.aop.api.request.AlipayCommerceEcEnterpriseInfoQueryRequest import AlipayCommerceEcEnterpriseInfoQueryRequest
  85. from alipay.aop.api.domain.AlipayCommerceEcEnterpriseInfoQueryModel import AlipayCommerceEcEnterpriseInfoQueryModel
  86. from alipay.aop.api.response.AlipayCommerceEcEnterpriseInfoQueryResponse import AlipayCommerceEcEnterpriseInfoQueryResponse
  87. model = AlipayCommerceEcEnterpriseInfoQueryModel()
  88. model.enterprise_id = enterprise_id
  89. request = AlipayCommerceEcEnterpriseInfoQueryRequest()
  90. request.biz_model = model
  91. client = AlipayClient.get_client()
  92. response = client.execute(request)
  93. if not response:
  94. raise CustomException(msg="查询企业详情失败: 无响应")
  95. result = AlipayCommerceEcEnterpriseInfoQueryResponse()
  96. result.parse_response_content(response)
  97. if not result.is_success():
  98. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  99. raise CustomException(msg=f"查询企业详情失败: {result.msg}")
  100. return result.enterprise_info # type: ignore
  101. @classmethod
  102. async def list_service(
  103. cls,
  104. auth: AuthSchema,
  105. page_no: int = 1,
  106. page_size: int = 20,
  107. search: dict | None = None,
  108. ) -> dict:
  109. """
  110. 查询企业列表
  111. """
  112. crud = EnterpriseCRUD(auth)
  113. offset = (page_no - 1) * page_size
  114. return await crud.page(
  115. offset=offset,
  116. limit=page_size,
  117. order_by=[{"id": "desc"}],
  118. search=search or {},
  119. out_schema=EnterpriseListOutSchema,
  120. )
  121. @classmethod
  122. async def update_service_from_alipay(
  123. cls, auth: AuthSchema, data: EnterpriseCreateOrUpdateSchema
  124. ) -> EnterpriseOperationOutSchema:
  125. """
  126. 修改企业信息
  127. 调用: alipay.commerce.ec.enterprise.info.modify (可选)
  128. """
  129. crud = EnterpriseCRUD(auth)
  130. data.validate_enterprise_id()
  131. enterprise_id = cast(str, data.enterprise_id)
  132. enterprise = await crud.get_by_enterprise_id(enterprise_id=enterprise_id)
  133. if not enterprise:
  134. enterprise = await EnterpriseService.create_service_from_alipay(auth, data)
  135. else:
  136. update_data = data.model_dump(exclude_none=True, exclude={"enterprise_id"})
  137. await crud.update_by_enterprise_id(enterprise_id, update_data)
  138. # 当状态为签约成功或认证时,调用支付宝查询企业详情
  139. if data.status and data.status in [
  140. EnterpriseStatusEnum.ENTERPRISE_ACTIVATED.value,
  141. EnterpriseStatusEnum.ENTERPRISE_AUTH.value
  142. ]:
  143. log.info(f"从支付宝平台查询企业详情: enterprise_id={enterprise_id}")
  144. enterprise_info = await EnterpriseService.detail_service(auth, enterprise_id)
  145. # 同步支付宝查询到的企业信息
  146. query_data = EnterpriseCreateOrUpdateSchema(
  147. # enterprise_id=result.enterprise_info.enterprise_id,
  148. account_id=enterprise_info.account_id,
  149. name=enterprise_info.enterprise_name,
  150. short_name=enterprise_info.enterprise_alias,
  151. )
  152. await crud.update_by_enterprise_id(
  153. enterprise_id, query_data.model_dump(exclude_none=True, exclude={"enterprise_id"}))
  154. return EnterpriseOperationOutSchema(enterprise_id=enterprise_id)
  155. @classmethod
  156. async def enterprise_unsign_service(
  157. cls, auth: AuthSchema, enterprise_id: str
  158. ) -> EnterpriseOperationOutSchema:
  159. """
  160. 企业解约
  161. 调用: alipay.commerce.ec.enterprise.unsign
  162. 注意: 支付宝异步通知后才会更新状态,此处仅调用接口
  163. """
  164. crud = EnterpriseCRUD(auth)
  165. enterprise = await crud.get_by_enterprise_id(enterprise_id)
  166. if not enterprise:
  167. raise CustomException(msg="企业不存在")
  168. if enterprise.status == EnterpriseStatusEnum.ENTERPRISE_UNSIGN.value:
  169. raise CustomException(msg="企业已解约")
  170. if enterprise.status == EnterpriseStatusEnum.ENTERPRISE_WITHDRAW.value:
  171. raise CustomException(msg="企业已注销,无法解约")
  172. client = AlipayClient.get_client()
  173. from alipay.aop.api.request.AlipayCommerceEcEnterpriseUnsignRequest import (
  174. AlipayCommerceEcEnterpriseUnsignRequest,
  175. )
  176. from alipay.aop.api.domain.AlipayCommerceEcEnterpriseUnsignModel import (
  177. AlipayCommerceEcEnterpriseUnsignModel,
  178. )
  179. from alipay.aop.api.response.AlipayCommerceEcEnterpriseUnsignResponse import (
  180. AlipayCommerceEcEnterpriseUnsignResponse,
  181. )
  182. model = AlipayCommerceEcEnterpriseUnsignModel()
  183. model.enterprise_id = enterprise.enterprise_id
  184. request = AlipayCommerceEcEnterpriseUnsignRequest()
  185. request.biz_model = model
  186. response = client.execute(request)
  187. if not response:
  188. raise CustomException(msg="企业解约申请失败: 无响应")
  189. result = AlipayCommerceEcEnterpriseUnsignResponse()
  190. result.parse_response_content(response)
  191. if not result.is_success():
  192. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  193. raise CustomException(msg=f"企业解约申请失败: {result.msg}")
  194. return EnterpriseOperationOutSchema(enterprise_id=enterprise.enterprise_id)
  195. @classmethod
  196. async def enterprise_delete_service(
  197. cls, auth: AuthSchema, enterprise_id: str
  198. ) -> EnterpriseOperationOutSchema:
  199. """
  200. 企业注销
  201. 调用: alipay.commerce.ec.enterprise.delete
  202. 注意: 支付宝异步通知后才会更新状态,此处仅调用接口
  203. """
  204. crud = EnterpriseCRUD(auth)
  205. enterprise = await crud.get_by_enterprise_id(enterprise_id)
  206. if not enterprise:
  207. raise CustomException(msg="企业不存在")
  208. if enterprise.status == EnterpriseStatusEnum.ENTERPRISE_WITHDRAW.value:
  209. raise CustomException(msg="企业已注销")
  210. if enterprise.status != EnterpriseStatusEnum.ENTERPRISE_UNSIGN.value:
  211. raise CustomException(msg="请先完成解约再注销")
  212. client = AlipayClient.get_client()
  213. from alipay.aop.api.request.AlipayCommerceEcEnterpriseDeleteRequest import (
  214. AlipayCommerceEcEnterpriseDeleteRequest,
  215. )
  216. from alipay.aop.api.domain.AlipayCommerceEcEnterpriseDeleteModel import (
  217. AlipayCommerceEcEnterpriseDeleteModel,
  218. )
  219. from alipay.aop.api.response.AlipayCommerceEcEnterpriseDeleteResponse import (
  220. AlipayCommerceEcEnterpriseDeleteResponse,
  221. )
  222. model = AlipayCommerceEcEnterpriseDeleteModel()
  223. model.enterprise_id = enterprise.enterprise_id
  224. request = AlipayCommerceEcEnterpriseDeleteRequest()
  225. request.biz_model = model
  226. response = client.execute(request)
  227. if not response:
  228. raise CustomException(msg="企业注销申请失败: 无响应")
  229. result = AlipayCommerceEcEnterpriseDeleteResponse()
  230. result.parse_response_content(response)
  231. if not result.is_success():
  232. log.error(f"支付宝接口调用失败: {result.code} - {result.msg}")
  233. raise CustomException(msg=f"企业注销申请失败: {result.msg}")
  234. return EnterpriseOperationOutSchema(enterprise_id=enterprise.enterprise_id)