saobei.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import traceback
  6. import simplejson as json
  7. from django.conf import settings
  8. from django.http import HttpResponse
  9. from typing import TYPE_CHECKING, Dict, Optional
  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.payment import PaymentGateway
  14. from apps.web.core.utils import async_operation
  15. from apps.web.exceptions import UserServerException
  16. from library.saobei import SaobeiException, SaobeiValidationError
  17. from taskmanager.mediator import task_caller
  18. if TYPE_CHECKING:
  19. from apps.web.common.transaction.pay import RechargeRecordT
  20. from apps.web.core.payment.saobei import SaobeiPaymentGateway
  21. logger = logging.getLogger(__name__)
  22. def update_record(record, **payload):
  23. # type: (RechargeRecordT, Dict)->bool
  24. if 'pay_time' in payload:
  25. import arrow
  26. finished_time = arrow.get(payload['pay_time'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive
  27. else:
  28. finished_time = datetime.datetime.now()
  29. return record.succeed(wxOrderNo = payload['out_trade_no'],
  30. transactionId = payload['channel_trade_no'],
  31. finishedTime = finished_time,
  32. **{
  33. 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  34. })
  35. class SaobeiPayRecordPoller(PayRecordPoller):
  36. def update_record(self, record, **payload):
  37. # type: (RechargeRecordT, Dict)->bool
  38. return update_record(record, **payload)
  39. def action_of_pay(self, payment_gateway, record):
  40. # type: (SaobeiPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
  41. try:
  42. pay_time = record.extraInfo.get('payTime') or record.attachParas.get('payTime')
  43. result = payment_gateway.api_trade_query(pay_trace = record.orderNo,
  44. pay_time = pay_time)
  45. if result['result_code'] == '01': # 成功
  46. return PayNotifyAction.NeedHandle, result
  47. if result['result_code'] == '02': # 失败
  48. return PayNotifyAction.NoHandle, {}
  49. return PayNotifyAction.Unknown, result
  50. except SaobeiException as e:
  51. logger.error(str(e))
  52. return PayNotifyAction.Unknown, {}
  53. except Exception as e:
  54. logger.exception(e)
  55. return PayNotifyAction.Unknown, {}
  56. class SaobeiPayNotifier(PayNotifier):
  57. def __init__(self, request, record_cls_factory):
  58. super(SaobeiPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
  59. def parse_request(self, request):
  60. return json.loads(request.body) # type: Dict
  61. @property
  62. def out_trade_no(self):
  63. return self.payload['terminal_trace']
  64. def verify(self, payment_gateway, record, payload):
  65. # type: (SaobeiPaymentGateway, RechargeRecordT, Dict)->None
  66. if record.fen_total_fee != int(payload['total_fee']):
  67. raise SaobeiValidationError(errmsg = u'无效的total_fee',
  68. lvalue = record.fen_total_fee,
  69. rvalue = payload['total_fee'],
  70. client = payment_gateway.client)
  71. def update_record(self, record, **payload):
  72. # type: (RechargeRecordT, Dict)->bool
  73. return update_record(record, **payload)
  74. @classmethod
  75. def reply(cls, ok, msg = None):
  76. return_code = '01' if ok else '02'
  77. if ok:
  78. return_msg = 'success'
  79. else:
  80. return_msg = msg if msg else 'unknown'
  81. return JsonResponse(data = {'return_code': return_code, 'return_msg': return_msg})
  82. def verify_sign(self, gateway, data):
  83. # type: (SaobeiPaymentGateway, dict)->bool
  84. from collections import OrderedDict
  85. sign_data = OrderedDict()
  86. sign_data['return_code'] = data['return_code']
  87. sign_data['return_msg'] = data['return_msg']
  88. sign_data['result_code'] = data['result_code']
  89. sign_data['pay_type'] = data['pay_type']
  90. sign_data['user_id'] = data['user_id']
  91. sign_data['merchant_name'] = data['merchant_name']
  92. sign_data['merchant_no'] = data['merchant_no']
  93. sign_data['terminal_id'] = data['terminal_id']
  94. sign_data['terminal_trace'] = data['terminal_trace']
  95. sign_data['terminal_time'] = data['terminal_time']
  96. sign_data['total_fee'] = data['total_fee']
  97. sign_data['end_time'] = data['end_time']
  98. sign_data['out_trade_no'] = data['out_trade_no']
  99. sign_data['channel_trade_no'] = data['channel_trade_no']
  100. sign_data['attach'] = data['attach']
  101. sign_data['key_sign'] = data['key_sign']
  102. return gateway.check(sign_data, order = False)
  103. def do(self, post_pay):
  104. # type: (callable)->JsonResponse
  105. try:
  106. payload = self.payload
  107. logger.debug('received saobei pay notify: {}'.format(str(payload)))
  108. if payload['return_code'] != '01':
  109. logger.error('return_code = {}, return_msg = {}'.format(payload['return_code'], payload['return_msg']))
  110. return self.reply(False, 'return_code != 01')
  111. order_no = payload['terminal_trace']
  112. record = self.record_cls.get_record(order_no = order_no) # type: Optional[RechargeRecordT]
  113. if not record:
  114. logger.error(
  115. 'no such record. orderNo = {}'.format(order_no))
  116. return self.reply(True)
  117. if record.is_success():
  118. logger.error('record has been finished. orderNo = {}'.format(order_no))
  119. return self.reply(True)
  120. payment_gateway = PaymentGateway.from_gateway_key(
  121. record.my_gateway,
  122. record.pay_gateway_key,
  123. record.pay_app_type) # type: SaobeiPaymentGateway
  124. # 校验签名
  125. self.verify_sign(payment_gateway, payload)
  126. # 校验参数
  127. self.verify(payment_gateway, record, payload)
  128. if payload['result_code'] != '01':
  129. record.fail(wxOrderNo = payload['channel_trade_no'], finishedTime = datetime.datetime.now(),
  130. description = payload['return_msg'])
  131. return self.reply(True)
  132. order_cache_mgr = OrderCacheMgr(record)
  133. try:
  134. if order_cache_mgr.check_and_set_done():
  135. logger.debug('{} has been done because cache in notify.'.format(repr(record)))
  136. return self.reply(True)
  137. except Exception as e:
  138. logger.error(
  139. 'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
  140. modified = self.update_record(record = record, **payload)
  141. if not modified:
  142. logger.debug('{} has been done because db in notify'.format(repr(record)))
  143. return self.reply(True)
  144. logger.info('{} successfully confirmed'.format(repr(record)))
  145. async_operation(post_pay, record = record)
  146. return self.reply(True)
  147. except SaobeiException as e:
  148. logger.error(str(e))
  149. return self.reply(True)
  150. except Exception as e:
  151. logger.exception(e)
  152. return self.reply(False, 'EXCEPTION')
  153. class SaobeiPullUp(PayPullUp):
  154. """
  155. 扫呗支付获取支付参数。目前只作为商户支付
  156. """
  157. def do(self): # type: ()->HttpResponse
  158. OrderCacheMgr(self.record).initial()
  159. try:
  160. pay_url = self.payment_gateway.create_pay_url(
  161. pay_trace = self.record.orderNo,
  162. pay_time = self.record.extraInfo['payTime'],
  163. money = self.record.money,
  164. attach = {'dealerId': self.record.ownerId},
  165. body = self.record.subject,
  166. notify_url = self.payload['notifyUrl'],
  167. front_url = self.payload['front_url'])
  168. except Exception as e:
  169. logger.exception(e)
  170. self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
  171. raise UserServerException(u'拉起支付发生异常')
  172. else:
  173. if not pay_url:
  174. self.record.fail(description = u'调起支付失败,请刷新后重试')
  175. raise UserServerException(u'调起支付失败,请刷新后重试')
  176. else:
  177. task_caller('poll_user_recharge_record',
  178. delay = PollRecordDefine.DELAY_BEFORE,
  179. expires = PollRecordDefine.TASK_EXPIRES,
  180. pay_app_type = self.payment_gateway.pay_app_type,
  181. record_id = str(self.record.id),
  182. interval = PollRecordDefine.WAIT_EACH_ROUND,
  183. total_count = PollRecordDefine.TOTAL_ROUNDS)
  184. return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': {'payUrl': pay_url}})