wechat.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import xmltodict
  6. from typing import TYPE_CHECKING
  7. from apps.web.common.transaction.refund import RefundNotifier, RefundPuller
  8. from apps.web.core.models import WechatPayApp
  9. from library.wechatbase.exceptions import WeChatException, WechatNetworkException
  10. from library.wechatpy.pay import WeChatPay
  11. logger = logging.getLogger(__name__)
  12. if TYPE_CHECKING:
  13. from django.core.handlers.wsgi import WSGIRequest
  14. from apps.web.core.payment.type_checking import PaymentGatewayT
  15. from apps.web.common.transaction.pay import RefundRecordT
  16. class WechatRefundNotifier(RefundNotifier):
  17. def parse_request(self, request): # type:(WSGIRequest) -> dict
  18. payload = xmltodict.parse(request.body)['xml'] # type: dict
  19. # xml 解析之后获取第一步的数据 找到wechat app
  20. app = WechatPayApp.objects.get(appid = payload["appid"], mchid = payload['mch_id']) # type: WechatPayApp
  21. req_info = WeChatPay(appid = app.appid, api_key = app.apikey, mch_id = app.mchid).decrypt(payload["req_info"])
  22. # 所有信息打包成一个字典返回
  23. payload.update(req_info)
  24. return payload
  25. def verify_payload(self, payload): # type:(dict) -> bool
  26. """ 忽略对签名串的校验 """
  27. return True
  28. def handle_refund_order(self, refundOrder, refund_post_callable): # type:(RefundRecordT, callable) -> None
  29. if not self.payload or self.payload["return_code"] != "SUCCESS":
  30. return
  31. if self.payload["refund_status"] == "SUCCESS":
  32. payFinishTime = self.payload["success_time"]
  33. datetimeRefund = datetime.datetime.strptime(payFinishTime, "%Y-%m-%d %H:%M:%S")
  34. matched = refundOrder.succeed(finishedTime = datetimeRefund, **{
  35. 'tradeRefundNo': self.payload["refund_id"]
  36. })
  37. if not matched:
  38. return
  39. return refund_post_callable(refundOrder, True)
  40. elif self.payload["refund_status"] == "REFUNDCLOSE":
  41. refundOrder.fail(
  42. errorCode = 'REFUNDCLOSE',
  43. errorDesc = self.payload["return_msg"],
  44. **{
  45. 'tradeRefundNo': self.payload.get("refund_id")
  46. })
  47. return
  48. elif self.payload["refund_status"] == "CHANGE":
  49. refundOrder.fail(
  50. errorCode = 'CHANGE',
  51. errorDesc = u'退款异常',
  52. **{
  53. 'tradeRefundNo': self.payload.get("refund_id")
  54. })
  55. else:
  56. pass
  57. @property
  58. def refund_order_filter(self): # type:() -> dict
  59. return {'orderNo': self.payload["out_refund_no"]}
  60. @property
  61. def errorResponse(self): # type:() -> str
  62. from apps.web.core.payment.wechat import WechatPaymentGateway
  63. return WechatPaymentGateway.reply("", False)
  64. @property
  65. def successResponse(self): # type:() -> str
  66. from apps.web.core.payment.wechat import WechatPaymentGateway
  67. return WechatPaymentGateway.reply("", True)
  68. class WechatRefundPuller(RefundPuller):
  69. def parse_error(self, errorCode, errorDesc, refund_post_callable):
  70. if errorCode == 'REFUNDNOTEXIST':
  71. self._refundOrder.no_order(errorCode = errorCode, errorDesc = errorDesc)
  72. elif errorCode in ['TRADE_OVERDUE', 'USER_ACCOUNT_ABNORMAL']:
  73. # USER_ACCOUNT_ABNORMAL: 用户账户异常或已注销,不能原路退回,请使用其他方式进行退款。
  74. # TRADE_OVERDUE: 超期订单无法退款
  75. matched = self._refundOrder.closed(errorCode = errorCode, errorDesc = errorDesc)
  76. if matched:
  77. refund_post_callable(self._refundOrder, False)
  78. return True
  79. else:
  80. if errorCode in ['NOTENOUGH']:
  81. # NOTENOUGH: 基本账户余额不足,请充值后重新发起
  82. self._refundOrder.fail(errorCode = errorCode, errorDesc = errorDesc)
  83. else:
  84. # 其他暂时不处理,逐步补充
  85. logger.warning('RefundOrder<orderNo={}>, errorCode = {}, errorDesc = {}'.format(
  86. self._refundOrder.orderNo, errorCode, errorDesc))
  87. return True
  88. return False
  89. def pull(self, refund_post_callable, **kwargs): # type:(callable, dict) -> bool
  90. payGateway = self._refundOrder.my_payment_gateway # type: PaymentGatewayT
  91. try:
  92. result = payGateway.api_refund_query(out_refund_no = self._refundOrder.orderNo)
  93. except WechatNetworkException as e:
  94. # return_code不为SUCCESS的情况下
  95. raise e
  96. except WeChatException as e:
  97. # result_code不为SUCCESS的情况下, 抛出异常
  98. return self.parse_error(e.errCode, e.errMsg, refund_post_callable)
  99. else:
  100. # 找出退款单号
  101. offset = None
  102. for _k, _v in result.items():
  103. if _v == self._refundOrder.orderNo:
  104. offset = _k.rsplit("_", 1)[1]
  105. break
  106. if not offset:
  107. return False
  108. refundStatus = result["refund_status_{}".format(offset)]
  109. if refundStatus == "SUCCESS":
  110. refundTime = result["refund_success_time_{}".format(offset)]
  111. datetimeRefund = datetime.datetime.strptime(refundTime, "%Y-%m-%d %H:%M:%S")
  112. matched = self._refundOrder.succeed(
  113. finishedTime = datetimeRefund, tradeRefundNo = result["refund_id_{}".format(offset)])
  114. if matched:
  115. refund_post_callable(self._refundOrder, True)
  116. return True
  117. elif refundStatus == "REFUNDCLOSE":
  118. self._refundOrder.fail(
  119. errorCode = "REFUNDCLOSE",
  120. errorDesc = '{}({})'.format(result.get("err_code_des"), result.get("err_code")))
  121. elif refundStatus == "PROCESSING":
  122. if not (self._refundOrder.is_processing or self._refundOrder.is_closed or self._refundOrder.is_success):
  123. self._refundOrder.processing()
  124. return True
  125. elif refundStatus == "CHANGE":
  126. matched = self._refundOrder.closed(
  127. errorCode = 'CHANGE',
  128. errorDesc = '{}({})'.format(result.get("err_code_des"), result.get("err_code")),
  129. **{
  130. 'tradeRefundNo': result.get("refund_id_{}".format(offset))
  131. })
  132. if matched:
  133. refund_post_callable(self._refundOrder, False)
  134. return True
  135. else:
  136. pass
  137. return False