__init__.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. import time
  5. from django.core.handlers.wsgi import WSGIRequest
  6. from django.http import HttpResponse
  7. from django.utils.module_loading import import_string
  8. from typing import TYPE_CHECKING, cast
  9. from apilib.systypes import StrEnum, Singleton
  10. from apilib.utils_json import JsonResponse
  11. from apps import lockCache
  12. from apps.web.core import PayAppType
  13. from apps.web.core.payment import PaymentGateway
  14. from apps.web.exceptions import UserServerException
  15. if TYPE_CHECKING:
  16. from apps.web.dealer.models import DealerRechargeRecord, RefundDealerRechargeRecord
  17. from apps.web.user.models import RechargeRecord, RefundMoneyRecord
  18. from typing import Union, Optional, Callable, Dict
  19. import six
  20. RechargeRecordT = Union[DealerRechargeRecord, RechargeRecord]
  21. RefundRecordT = Union[RefundDealerRechargeRecord, RefundMoneyRecord]
  22. from apps.web.core.payment import PaymentGatewayT
  23. logger = logging.getLogger(__name__)
  24. class PayNotifyAction(StrEnum):
  25. NeedHandle = 'needHandle'
  26. NoHandle = 'noHandle'
  27. Unknown = 'unknown'
  28. class OrderCacheMgr(object):
  29. KEY = '{}-{}-lock'
  30. def __init__(self, record_or_id, cls_name = None):
  31. # type: (Union[RechargeRecordT, basestring], Optional[str])->None
  32. if isinstance(record_or_id, basestring):
  33. assert cls_name, 'cls_name must not be none if record is string'
  34. self.key = self.KEY.format(cls_name, record_or_id)
  35. else:
  36. assert not cls_name, 'cls_name must be none if record is object'
  37. self.key = self.KEY.format(record_or_id.__class__.__name__, str(record_or_id.id))
  38. def initial(self):
  39. try:
  40. return lockCache.set(self.key, '0', 25 * 3600)
  41. except Exception as e:
  42. logger.exception(e)
  43. return 0
  44. def check_and_set_done(self):
  45. # type: ()->bool
  46. new_value = lockCache.incr(self.key)
  47. if int(new_value) >= 2:
  48. return True
  49. else:
  50. return False
  51. def has_done(self):
  52. # type: ()->bool
  53. new_value = lockCache.get(self.key, None)
  54. if not new_value:
  55. return False
  56. if int(new_value) >= 1:
  57. return True
  58. else:
  59. return False
  60. class PayRecordPoller(object):
  61. def __init__(self, record_id, interval, total_count, record_cls, post_pay):
  62. # type: (str, int, int, six.class_types, Callable)->None
  63. self.record_id = record_id
  64. self.interval = interval
  65. self.total_count = total_count
  66. self.record_cls = record_cls
  67. self.post_pay = post_pay
  68. def update_record(self, record, **payload):
  69. # type: (RechargeRecordT, Dict)->bool
  70. raise NotImplementedError('sub class must implement update_record')
  71. def action_of_pay(self, payment_gateway, record):
  72. # type: (PaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
  73. raise NotImplementedError('sub class must implement action_of_pay')
  74. def start(self):
  75. logger.debug('poll record<id={}> pay status.'.format(self.record_id))
  76. count = 0
  77. order_cache_mgr = OrderCacheMgr(self.record_id, self.record_cls.__name__)
  78. if order_cache_mgr.has_done():
  79. logger.debug('record<id={}> has done.'.format(self.record_id))
  80. return
  81. record = self.record_cls.objects(id = self.record_id).first() # type: RechargeRecordT
  82. if not record:
  83. logger.error('record<id={}> is not exist.'.format(self.record_id))
  84. return
  85. if record.is_success():
  86. logger.error('record<id={},order={}> is success.'.format(self.record_id, record.orderNo))
  87. return
  88. payment_gateway = PaymentGateway.from_gateway_key(
  89. record.my_gateway,
  90. record.pay_gateway_key,
  91. record.pay_app_type) # type: PaymentGatewayT
  92. while count < self.total_count:
  93. try:
  94. if count != 0 and order_cache_mgr.has_done():
  95. logger.debug('record<id={},order={}> has done.'.format(self.record_id, record.orderNo))
  96. return
  97. action, query_trade_result = self.action_of_pay(payment_gateway, record)
  98. logger.debug(
  99. 'record<id={},order={}> trade info: trade result = {}, action = {}.'.format(
  100. self.record_id, record.orderNo, str(query_trade_result), action))
  101. if action == PayNotifyAction.NoHandle:
  102. return
  103. if action == PayNotifyAction.Unknown:
  104. continue
  105. try:
  106. if order_cache_mgr.check_and_set_done():
  107. logger.debug(
  108. 'record<id={},order={}> check has done.'.format(self.record_id, record.orderNo))
  109. return
  110. except Exception as e:
  111. logger.error(
  112. 'record<id={},order={}> cache key is not exist or exception. exception = {}, key = {}'.format(
  113. self.record_id, record.orderNo, str(e), order_cache_mgr.key))
  114. modified = self.update_record(record, **query_trade_result)
  115. if not modified:
  116. logger.debug(
  117. 'record<id={},order={}> has been modified.'.format(self.record_id, record.orderNo))
  118. return
  119. return self.post_pay(record)
  120. except Exception as e:
  121. logger.exception(
  122. 'record<id={},order={}> poll trade status failure. e = {}'.format(self.record_id, record.orderNo,
  123. str(e)))
  124. finally:
  125. count = count + 1
  126. time.sleep(self.interval)
  127. class PayNotifier(object):
  128. def __init__(self, request, record_cls_factory):
  129. # type: (WSGIRequest, callable)->None
  130. self.payload = self.parse_request(request)
  131. self.record_cls = record_cls_factory(self.out_trade_no)
  132. def parse_request(self, request):
  133. # type: (WSGIRequest)->dict
  134. raise NotImplementedError('sub class must implement update_record')
  135. @property
  136. def out_trade_no(self):
  137. raise AttributeError('no out_trade_no attr.')
  138. def update_record(self, record, **payload):
  139. # type: (RechargeRecordT, Dict)->bool
  140. raise NotImplementedError('sub class must implement update_record')
  141. def do(self, post_pay):
  142. # type: (callable)->HttpResponse
  143. raise NotImplementedError('sub class must implement do')
  144. class PayPullUpFactoryIntf(object):
  145. @classmethod
  146. def create(cls, payment_gateway, record, **payload):
  147. # type: (PaymentGatewayT, RechargeRecordT, dict)->PayPullUp
  148. raise NotImplementedError('sub class must implement create')
  149. class PayPullUp(object):
  150. def __init__(self, openId, payment_gateway, record, **payload):
  151. # type: (basestring, PaymentGatewayT, RechargeRecordT, dict)->None
  152. self.openId = openId
  153. self.record = record
  154. self.payment_gateway = payment_gateway
  155. self.payload = payload
  156. @classmethod
  157. def create(cls, factory_cls, payment_gateway, record, **payload):
  158. # type: (cast(PayPullUpFactoryIntf), PaymentGatewayT, RechargeRecordT, dict)->PayPullUp
  159. return factory_cls.create(payment_gateway, record, **payload)
  160. def do(self):
  161. # type: ()->HttpResponse
  162. raise NotImplementedError('sub class must implement do')
  163. class PayManager(Singleton):
  164. def __init__(self):
  165. super(PayManager, self).__init__()
  166. self.map_dict = {
  167. PayAppType.WECHAT: {
  168. 'poller': import_string('apps.web.common.transaction.pay.wechat.WechatPayRecordPoller'),
  169. 'notifier': import_string('apps.web.common.transaction.pay.wechat.WechatPayNotifier'),
  170. 'pullup': import_string('apps.web.common.transaction.pay.wechat.WechatPullUp'),
  171. },
  172. PayAppType.WECHAT_MINI: {
  173. 'poller': import_string('apps.web.common.transaction.pay.wechat.WechatPayRecordPoller'),
  174. 'notifier': import_string('apps.web.common.transaction.pay.wechat.WechatPayNotifier'),
  175. },
  176. PayAppType.ALIPAY: {
  177. 'poller': import_string('apps.web.common.transaction.pay.alipay.AliPayRecordPoller'),
  178. 'notifier': import_string('apps.web.common.transaction.pay.alipay.AliPayNotifier'),
  179. 'pullup': import_string('apps.web.common.transaction.pay.alipay.AlipayPullUp')
  180. }
  181. }
  182. def get_poller(self, pay_app_type):
  183. assert pay_app_type in self.map_dict, 'not register pay app type'
  184. return self.map_dict[pay_app_type]['poller']
  185. def get_notifier(self, pay_app_type):
  186. assert pay_app_type in self.map_dict, 'not register pay app type'
  187. return self.map_dict[pay_app_type]['notifier']
  188. def get_pull_up(self, pay_app_type):
  189. if pay_app_type not in self.map_dict:
  190. raise UserServerException(u'第三方支付配置错误,请联系平台客服(1002)')
  191. return self.map_dict[pay_app_type]['pullup']
  192. class RefundManager(Singleton):
  193. def __init__(self):
  194. super(RefundManager, self).__init__()
  195. self.map_dict = {
  196. PayAppType.WECHAT:
  197. {
  198. 'poller': import_string('apps.web.common.transaction.refund.wechat.WechatRefundPuller'),
  199. 'notifier': import_string('apps.web.common.transaction.refund.wechat.WechatRefundNotifier'),
  200. },
  201. PayAppType.ALIPAY: {
  202. 'poller': import_string('apps.web.common.transaction.refund.alipay.AliRefundPuller'),
  203. 'notifier': import_string('apps.web.common.transaction.refund.alipay.AliRefundNotifier'),
  204. }
  205. }
  206. def get_poller(self, pay_app_type):
  207. assert pay_app_type in self.map_dict, 'not register pay app type'
  208. return self.map_dict[pay_app_type]['poller']
  209. def get_notifier(self, pay_app_type):
  210. assert pay_app_type in self.map_dict, 'not register pay app type'
  211. return self.map_dict[pay_app_type]['notifier']