ys.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp
  12. from apps.web.constant import AppPlatformType, PollRecordDefine
  13. from apps.web.core import PayAppType
  14. from apps.web.core.payment import PaymentGateway
  15. from apps.web.core.utils import async_operation, JsonOkResponse
  16. from apps.web.exceptions import UserServerException
  17. from library.ys import YsException
  18. from taskmanager.mediator import task_caller
  19. if TYPE_CHECKING:
  20. from apps.web.common.transaction.pay import RechargeRecordT
  21. from apps.web.core.payment.ys import YsPaymentGateway
  22. logger = logging.getLogger(__name__)
  23. def update_record(record, **payload):
  24. # type: (RechargeRecordT, Dict)->bool
  25. if 'wxtimeend' in payload:
  26. import arrow
  27. finished_time = arrow.get(payload['wxtimeend'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive
  28. else:
  29. finished_time = datetime.datetime.now()
  30. return record.succeed(wxOrderNo = payload['wtorderid'],
  31. transactionId = payload['wxtransactionid'],
  32. finishedTime = finished_time,
  33. **{
  34. 'extraInfo.wxopenid': payload['wxopenid'],
  35. 'extraInfo.acctype': payload['acctype']
  36. })
  37. class YsReordPoller(PayRecordPoller):
  38. def update_record(self, record, **payload):
  39. # type: (RechargeRecordT, Dict)->bool
  40. return update_record(record, **payload)
  41. def action_of_pay(self, payment_gateway, record):
  42. # type: (YsPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
  43. '''
  44. 00 - 成功;98 - 未确定;25 - 找不到原笔交易;AA-待支付;其他 - 失败
  45. :param payment_gateway:
  46. :param record:
  47. :return:
  48. '''
  49. try:
  50. result = payment_gateway.api_trade_query(out_trade_no = record.orderNo)
  51. if 'resultcode' not in result:
  52. return PayNotifyAction.Unknown, result
  53. if result['resultcode'] == '00':
  54. return PayNotifyAction.NeedHandle, result
  55. if result['resultcode'] in ['98', 'AA']:
  56. return PayNotifyAction.Unknown, result
  57. if result['resultcode'] == '25':
  58. return PayNotifyAction.NoHandle, result
  59. # 其他说明交易失败, 不处理
  60. return PayNotifyAction.NoHandle, result
  61. except YsException as e:
  62. logger.error(str(e))
  63. return PayNotifyAction.Unknown, {}
  64. except Exception, e:
  65. logger.exception(e)
  66. return PayNotifyAction.Unknown, {}
  67. class YsNotifier(PayNotifier):
  68. def __init__(self, request, record_cls_factory):
  69. super(YsNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
  70. def parse_request(self, request):
  71. return json.loads(request.body) # type: Dict
  72. @property
  73. def out_trade_no(self):
  74. return self.payload['tradetrace']
  75. def verify(self, payment_gateway, record, payload):
  76. # type: (YsPaymentGateway, RechargeRecordT, Dict)->None
  77. pass
  78. def update_record(self, record, **payload):
  79. # type: (RechargeRecordT, Dict)->bool
  80. return update_record(record, **payload)
  81. def reply(self, retry = False):
  82. if retry:
  83. return JsonResponse({'resultcode': ''})
  84. else:
  85. return JsonResponse({'resultcode': '00'})
  86. def verify_sign(self, gateway, data):
  87. # type: (YsPaymentGateway, dict)->bool
  88. sign = data.pop('sign')
  89. return gateway.client.sign(data) == sign
  90. def do(self, post_pay):
  91. # type: (callable)->JsonResponse
  92. try:
  93. payload = self.payload
  94. logger.debug('received ys pay notify: {}'.format(str(payload)))
  95. order_no = payload['tradetrace']
  96. record = self.record_cls.get_record(order_no = order_no) # type: Optional[RechargeRecordT]
  97. if not record:
  98. logger.error(
  99. 'no such record. orderNo = {}'.format(order_no))
  100. return self.reply(True)
  101. if record.is_success():
  102. logger.error('record has been finished. orderNo = {}'.format(order_no))
  103. return self.reply(True)
  104. payment_gateway = PaymentGateway.from_gateway_key(
  105. record.my_gateway,
  106. record.pay_gateway_key,
  107. record.pay_app_type) # type: YsPaymentGateway
  108. # 校验签名
  109. self.verify_sign(payment_gateway, payload)
  110. # 校验参数
  111. self.verify(payment_gateway, record, payload)
  112. order_cache_mgr = OrderCacheMgr(record)
  113. try:
  114. if order_cache_mgr.check_and_set_done():
  115. logger.debug('{} has been done because cache in notify.'.format(repr(record)))
  116. return self.reply()
  117. except Exception as e:
  118. logger.error(
  119. 'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
  120. modified = self.update_record(record = record, **payload)
  121. if not modified:
  122. logger.debug('{} has been done because db in notify'.format(repr(record)))
  123. return self.reply()
  124. logger.info('{} successfully confirmed'.format(repr(record)))
  125. async_operation(post_pay, record = record)
  126. return self.reply(True)
  127. except YsException as e:
  128. logger.error(str(e))
  129. return self.reply()
  130. except Exception as e:
  131. logger.exception(e)
  132. return self.reply(retry = True)
  133. class YsPullUp(PayPullUp):
  134. """
  135. 易生获取支付参数。只用作商户,所以GOLDEN为True
  136. """
  137. def do(self): # type: ()->HttpResponse
  138. OrderCacheMgr(self.record).initial()
  139. try:
  140. wtorderid, pay_info = self.payment_gateway.unified_order(
  141. out_trade_no = self.record.orderNo,
  142. money = self.record.money,
  143. notify_url = self.payload['notifyUrl'],
  144. subject = self.record.subject,
  145. openId = self.openId)
  146. except Exception as e:
  147. logger.exception(e)
  148. self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
  149. raise UserServerException(u'拉起支付发生异常')
  150. else:
  151. logger.debug("wt order id is {}. pay info = {}".format(wtorderid, pay_info))
  152. response = None
  153. if self.payment_gateway.gateway_type == AppPlatformType.ALIPAY:
  154. response = JsonOkResponse(payload = {
  155. 'tradeNO': pay_info['tradeNO'],
  156. 'outTradeNo': self.record.orderNo,
  157. 'adShow': self.payload.get('showAd', False)
  158. })
  159. elif self.payment_gateway.gateway_type == AppPlatformType.WECHAT:
  160. pay_info.update({
  161. 'outTradeNo': self.record.orderNo,
  162. 'golden': True,
  163. 'adShow': self.payload.get('showAd', False)
  164. })
  165. response = JsonOkResponse(payload = pay_info)
  166. if not response:
  167. self.record.fail(description = u'不支持该支付类型')
  168. raise UserServerException(u'不支持该支付类型')
  169. task_caller('poll_user_recharge_record',
  170. delay = PollRecordDefine.DELAY_BEFORE,
  171. expires = PollRecordDefine.TASK_EXPIRES,
  172. pay_app_type = self.payment_gateway.pay_app_type,
  173. record_id = str(self.record.id),
  174. interval = PollRecordDefine.WAIT_EACH_ROUND,
  175. total_count = PollRecordDefine.TOTAL_ROUNDS)
  176. return response