dlb.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import traceback
  6. from django.conf import settings
  7. from django.http import QueryDict, HttpResponse
  8. from typing import TYPE_CHECKING, Dict, Optional
  9. from apilib.monetary import RMB
  10. from apilib.utils_json import JsonResponse
  11. from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp
  12. from apps.web.constant import PollRecordDefine
  13. from apps.web.core.utils import async_operation
  14. from apps.web.exceptions import UserServerException
  15. from library.dlb import DlbPayException, DlbErrorCode, DlbValidationError
  16. from apps.web.core.payment import PaymentGateway
  17. from taskmanager.mediator import task_caller
  18. if TYPE_CHECKING:
  19. from apps.web.common.transaction.pay import RechargeRecordT
  20. from typing import Dict
  21. from apps.web.core.payment import PaymentGatewayT
  22. from apps.web.core.payment.dlb import DlbPaymentGateway
  23. logger = logging.getLogger(__name__)
  24. def update_record(record, **payload):
  25. # type: (RechargeRecordT, Dict)->bool
  26. if 'completeTime' in payload:
  27. import arrow
  28. finished_time = arrow.get(payload['completeTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
  29. else:
  30. finished_time = datetime.datetime.now()
  31. return record.succeed(wxOrderNo = payload['orderNum'],
  32. transactionId = payload['bankOutTradeNum'],
  33. finishedTime = finished_time,
  34. **{
  35. 'extraInfo.bankTradeNum': payload['bankTradeNum'],
  36. 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  37. })
  38. class DlbPayReordPoller(PayRecordPoller):
  39. def update_record(self, record, **payload):
  40. # type: (RechargeRecordT, Dict)->bool
  41. return update_record(record, **payload)
  42. def action_of_pay(self, payment_gateway, record):
  43. # type: (DlbPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
  44. try:
  45. result = payment_gateway.api_trade_query(out_trade_no = record.orderNo)
  46. if 'status' not in result:
  47. return PayNotifyAction.Unknown, result
  48. if result['status'] == 'SUCCESS':
  49. return PayNotifyAction.NeedHandle, {
  50. 'orderNum': result['orderNum'],
  51. 'bankTradeNum': result['payRecordList'][0]['bankTradeNum'],
  52. 'bankOutTradeNum': result['payRecordList'][0]['bankOutTradeNum']
  53. }
  54. if result['status'] == 'FAIL':
  55. return PayNotifyAction.NoHandle, result
  56. if result['status'] == 'INIT':
  57. if len(result['payRecordList']) > 0 and result['payRecordList'][0]['payStatus'] == 'FAIL':
  58. return PayNotifyAction.NoHandle, result
  59. else:
  60. return PayNotifyAction.Unknown, result
  61. if result['status'] in ['REFUND', 'CLOSE', 'CANCLE']:
  62. return PayNotifyAction.NoHandle, result
  63. return PayNotifyAction.Unknown, result
  64. except DlbPayException as e:
  65. logger.error(str(e))
  66. return PayNotifyAction.Unknown, {}
  67. except Exception, e:
  68. logger.exception(e)
  69. return PayNotifyAction.Unknown, {}
  70. class DlbPayNotifier(PayNotifier):
  71. def __init__(self, request, record_cls_factory):
  72. self.timestamp = request.META.get('HTTP_TIMESTAMP')
  73. self.token = request.META.get('HTTP_TOKEN')
  74. super(DlbPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
  75. def parse_request(self, request):
  76. return request.GET # type: QueryDict
  77. @property
  78. def out_trade_no(self):
  79. return self.payload['requestNum']
  80. def verify(self, payment_gateway, record, payload):
  81. # type: (PaymentGatewayT, RechargeRecordT, Dict)->None
  82. notifier_fen = int(RMB(payload['orderAmount']) * 100)
  83. if record.fen_total_fee != notifier_fen:
  84. raise DlbValidationError(
  85. errorMsg = u'invalid orderAmount',
  86. lvalue = record.fen_total_fee,
  87. rvalue = notifier_fen,
  88. client = payment_gateway._app)
  89. def update_record(self, record, **payload):
  90. # type: (RechargeRecordT, Dict)->bool
  91. return update_record(record, **payload)
  92. def reply(self):
  93. return JsonResponse(data = {})
  94. def verify_sign(self, gateway, data, token):
  95. # type: (DlbPaymentGateway, dict, str)->bool
  96. return gateway.check(data, token)
  97. def do(self, post_pay):
  98. # type: (callable)->JsonResponse
  99. try:
  100. payload = self.payload
  101. logger.debug('received dlb pay notify: %s' % str(payload))
  102. if payload['status'] != 'SUCCESS':
  103. return self.reply()
  104. order_no = payload['requestNum']
  105. record = self.record_cls.get_record(order_no = order_no) # type: Optional[RechargeRecordT]
  106. if not record:
  107. logger.error('no such record<orderNo={}>'.format(order_no))
  108. return self.reply()
  109. if record.is_success():
  110. logger.error(
  111. 'record<id={},orderNo={}> has been finished.'.format(str(record.id), order_no))
  112. return self.reply()
  113. payment_gateway = PaymentGateway.from_gateway_key(
  114. record.my_gateway,
  115. record.pay_gateway_key,
  116. record.pay_app_type) # type: DlbPaymentGateway
  117. if not self.verify_sign(gateway = payment_gateway,
  118. data = {
  119. 'timestamp': self.timestamp
  120. },
  121. token = self.token):
  122. raise DlbPayException(errorCode = DlbErrorCode.MY_ERROR_SIGNATURE,
  123. errorMsg = u'TOKEN校验错误',
  124. client = payment_gateway._app)
  125. self.verify(payment_gateway, record, payload)
  126. order_cache_mgr = OrderCacheMgr(record)
  127. try:
  128. if order_cache_mgr.check_and_set_done():
  129. logger.debug('{} has been done because cache in notify.'.format(repr(record)))
  130. return self.reply()
  131. except Exception as e:
  132. logger.error(
  133. 'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
  134. modified = self.update_record(record, **payload)
  135. if not modified:
  136. # 如果没有任何记录被更新则说明已经处理了
  137. logger.debug('{} has been done because db in notify'.format(repr(record)))
  138. return self.reply()
  139. logger.info('{} successfully confirmed'.format(repr(record)))
  140. async_operation(post_pay, record = record)
  141. return self.reply()
  142. except DlbPayException as e:
  143. logger.error(str(e))
  144. return self.reply()
  145. except Exception as e:
  146. logger.exception(e)
  147. return self.reply()
  148. class DlbPullUp(PayPullUp):
  149. """
  150. 哆啦宝支付获取支付参数。目前只作为商户支付
  151. """
  152. def do(self): # type: ()->HttpResponse
  153. OrderCacheMgr(self.record).initial()
  154. try:
  155. pay_url = self.payment_gateway.create_pay_url(
  156. order_no = self.record.orderNo,
  157. money = self.record.money,
  158. attach = {'dealerId': self.record.ownerId},
  159. notify_url = self.payload.get('notifyUrl'),
  160. front_url = self.payload.get('front_url'))
  161. except Exception as e:
  162. logger.exception(e)
  163. self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
  164. raise UserServerException(u'拉起支付发生异常')
  165. else:
  166. if not pay_url:
  167. self.record.fail(description = u'调起支付失败,请刷新后重试')
  168. raise UserServerException(u'调起支付失败,请刷新后重试')
  169. else:
  170. task_caller('poll_user_recharge_record',
  171. delay = PollRecordDefine.DELAY_BEFORE,
  172. expires = PollRecordDefine.TASK_EXPIRES,
  173. pay_app_type = self.payment_gateway.pay_app_type,
  174. record_id = str(self.record.id),
  175. interval = PollRecordDefine.WAIT_EACH_ROUND,
  176. total_count = PollRecordDefine.TOTAL_ROUNDS)
  177. return JsonResponse({
  178. 'result': 1,
  179. 'description': 'SUCCESS',
  180. 'payload': {
  181. 'payType': self.payment_gateway.pay_app_type,
  182. 'payUrl': pay_url
  183. }})