jdopen.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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.monetary import RMB
  11. from apilib.utils_json import JsonResponse
  12. from apps.web.common.transaction.pay import PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp
  13. from apps.web.constant import AppPlatformType, PollRecordDefine
  14. from apps.web.core import PayAppType
  15. from apps.web.core.payment import PaymentGateway
  16. from apps.web.core.utils import async_operation, JsonOkResponse
  17. from apps.web.exceptions import UserServerException
  18. from library.jdopen.exceptions import JdOpenException, JDOpenValidationError
  19. from taskmanager.mediator import task_caller
  20. if TYPE_CHECKING:
  21. from apps.web.common.transaction.pay import RechargeRecordT
  22. from apps.web.core.payment.jdopen import JDOpenPaymentGateway
  23. logger = logging.getLogger(__name__)
  24. def update_record(record, **payload):
  25. # type: (RechargeRecordT, Dict)->bool
  26. import arrow
  27. finished_time = arrow.get(payload['completeTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo=settings.TIME_ZONE).naive
  28. jdOrderNo = payload['orderNum'] # 京东系统订单号. 京东系统查单
  29. if 'bankOutTradeNum' in payload and payload['bankOutTradeNum']:
  30. transactionId = payload['bankOutTradeNum']
  31. elif 'bankRequestNum' in payload and payload['bankRequestNum']:
  32. transactionId = payload['bankRequestNum']
  33. else:
  34. transactionId = jdOrderNo
  35. _payload = {
  36. 'extraInfo.bankRequestNum': payload.get('bankRequestNum', ''),
  37. 'extraInfo.bankOutTradeNum': payload.get('bankOutTradeNum', ''),
  38. 'extraInfo.payload': payload,
  39. 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  40. }
  41. if 'ledgerStatus' in payload and payload['ledgerStatus']:
  42. _payload.update({
  43. 'extraInfo.ledgerStatus': payload['ledgerStatus']
  44. })
  45. return record.succeed(wxOrderNo=jdOrderNo,
  46. transactionId=transactionId,
  47. finishedTime=finished_time,
  48. **_payload)
  49. class JDOpenPayReordPoller(PayRecordPoller):
  50. def update_record(self, record, **payload):
  51. # type: (RechargeRecordT, Dict)->bool
  52. return update_record(record, **payload)
  53. def action_of_pay(self, payment_gateway, record):
  54. # type: (JDOpenPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
  55. try:
  56. result = payment_gateway.api_trade_query(out_trade_no=record.orderNo)
  57. if 'status' not in result:
  58. return PayNotifyAction.Unknown, result
  59. if result['status'] == 'SUCCESS':
  60. return PayNotifyAction.NeedHandle, {
  61. 'completeTime': result['completeTime'],
  62. 'orderNum': result['orderNum'],
  63. 'bankRequestNum': result['payRecordList'][0]['bankRequestNum'],
  64. 'bankOutTradeNum': result['payRecordList'][0]['bankOutTradeNum']
  65. }
  66. if result['payStatus'] in ['FAIL', 'REFUND', 'CANCEL', 'CLOSE']:
  67. return PayNotifyAction.NoHandle, result
  68. return PayNotifyAction.Unknown, result
  69. except JdOpenException as e:
  70. logger.error(str(e))
  71. return PayNotifyAction.Unknown, {}
  72. except Exception, e:
  73. logger.exception(e)
  74. return PayNotifyAction.Unknown, {}
  75. class JDOpenPayNotifier(PayNotifier):
  76. def __init__(self, request, record_cls_factory):
  77. super(JDOpenPayNotifier, self).__init__(request=request, record_cls_factory=record_cls_factory)
  78. self.body = request.body
  79. def parse_request(self, request):
  80. _head = {
  81. 'accessKey': request.META['HTTP_ACCESSKEY'],
  82. 'version': request.META['HTTP_VERSION'],
  83. 'timestamp': request.META['HTTP_TIMESTAMP'],
  84. 'token': request.META['HTTP_TOKEN']
  85. }
  86. _body = json.loads(request.body) # type: Dict
  87. logger.debug('received jdopen pay notify: body = {}; heads = {}'.format(_body, _head))
  88. return {
  89. 'body': _body,
  90. 'head': _head
  91. }
  92. @property
  93. def out_trade_no(self):
  94. return self.payload['body']['requestNum']
  95. def verify(self, payment_gateway, record, payload):
  96. # type: (JDOpenPaymentGateway, RechargeRecordT, Dict)->None
  97. notifier_yuan = RMB(payload['body']['orderAmount'])
  98. if record.money != notifier_yuan:
  99. raise JDOpenValidationError(
  100. tips=u'无效的total_fee',
  101. lvalue=str(record.money),
  102. rvalue=str(notifier_yuan),
  103. client=payment_gateway.client)
  104. def update_record(self, record, **payload):
  105. # type: (RechargeRecordT, Dict)->bool
  106. return update_record(record, **payload)
  107. def reply(self):
  108. return HttpResponse('ok')
  109. def check_and_update_ledger_status(self, record):
  110. if 'ledgerStatus' in self.payload['body'] and self.payload['body']['ledgerStatus']:
  111. old_ledger_status = record.extraInfo.get('ledgerStatus', '')
  112. if old_ledger_status != self.payload['body']['ledgerStatus']:
  113. record.update(extraInfo__ledgerStatus = self.payload['body']['ledgerStatus'])
  114. def do(self, post_pay):
  115. # type: (callable)->HttpResponse
  116. try:
  117. order_no = self.out_trade_no
  118. record = self.record_cls.get_record(order_no=order_no) # type: Optional[RechargeRecordT]
  119. if not record:
  120. logger.error('no such record<orderNo={}>'.format(order_no))
  121. return self.reply()
  122. if self.payload['body']['status'] != 'SUCCESS':
  123. logger.error('status of record<orderNo={}> is not success.'.format(order_no))
  124. return self.reply()
  125. if record.is_success():
  126. logger.error(
  127. 'record<id={},orderNo={}> has been finished.'.format(str(record.id), order_no))
  128. self.check_and_update_ledger_status(record)
  129. return self.reply()
  130. pay_gateway = PaymentGateway.from_gateway_key(
  131. record.my_gateway,
  132. record.pay_gateway_key,
  133. record.pay_app_type) # type: JDOpenPaymentGateway
  134. pay_gateway.check_token(self.body, self.payload['head']['timestamp'], self.payload['head']['token'])
  135. self.verify(pay_gateway, record, self.payload)
  136. order_cache_mgr = OrderCacheMgr(record)
  137. try:
  138. if order_cache_mgr.check_and_set_done():
  139. logger.debug('{} has been done because cache in notify.'.format(repr(record)))
  140. self.check_and_update_ledger_status(record)
  141. return self.reply()
  142. except Exception as e:
  143. logger.error(
  144. 'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key))
  145. modified = self.update_record(record, **(self.payload['body']))
  146. if not modified:
  147. # 如果没有任何记录被更新则说明已经处理了
  148. logger.debug('{} has been done because db in notify'.format(repr(record)))
  149. self.check_and_update_ledger_status(record)
  150. return self.reply()
  151. logger.info('{} successfully confirmed'.format(repr(record)))
  152. async_operation(post_pay, record=record)
  153. return self.reply()
  154. except JdOpenException as e:
  155. logger.exception(e)
  156. return self.reply()
  157. except Exception as e:
  158. logger.exception(e)
  159. class JDOpenPullUp(PayPullUp):
  160. def do(self): # type: ()->HttpResponse
  161. gateway_type = self.payment_gateway.gateway_type
  162. if gateway_type not in [AppPlatformType.ALIPAY, AppPlatformType.WECHAT, AppPlatformType.JD]:
  163. raise UserServerException(u'不支持该支付类型')
  164. OrderCacheMgr(self.record).initial()
  165. try:
  166. ledger_type, ledger_fee_assume, ledger_list = self.payment_gateway.bill_split_rule(
  167. self.record.partition_map)
  168. _payload = {
  169. 'out_trade_no': self.record.orderNo,
  170. 'money': self.record.money,
  171. 'notify_url': self.payload['notifyUrl'],
  172. 'subject': self.record.subject,
  173. 'attach': {'dealerId': self.record.ownerId},
  174. 'openId': self.openId
  175. }
  176. if ledger_list:
  177. _payload.update({
  178. 'ledgerRule': {
  179. 'ledgerType': ledger_type,
  180. 'ledgerFeeAssume': ledger_fee_assume,
  181. 'list': ledger_list
  182. }})
  183. pay_info = self.payment_gateway.unified_order(**_payload)
  184. except JdOpenException as e:
  185. logger.exception(e)
  186. self.record.fail(description = e.errMsg)
  187. raise UserServerException(e.errMsg)
  188. except Exception as e:
  189. logger.exception(e)
  190. self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
  191. raise UserServerException(u'拉起支付发生异常')
  192. else:
  193. logger.debug('jdopen unified order pay info = {}'.format(str(pay_info)))
  194. self.record.update(
  195. wxOrderNo = pay_info['data']['bankRequestNum'], extraInfo__jdOrderNo = pay_info['data']['orderNum'])
  196. gateway_type = self.payment_gateway.gateway_type
  197. if gateway_type == AppPlatformType.ALIPAY:
  198. response = JsonOkResponse(payload = {
  199. 'tradeNO': pay_info['data']['bankRequest']['TRADENO'],
  200. 'outTradeNo': self.record.orderNo,
  201. 'adShow': self.payload.get('showAd')
  202. })
  203. elif gateway_type == AppPlatformType.WECHAT:
  204. response = JsonOkResponse(payload = {
  205. 'outTradeNo': self.record.orderNo,
  206. 'golden': True,
  207. 'adShow': self.payload.get('showAd'),
  208. 'appId': pay_info['data']['bankRequest']['APPID'],
  209. 'timeStamp': pay_info['data']['bankRequest']['TIMESTAMP'],
  210. 'nonceStr': pay_info['data']['bankRequest']['NONCESTR'],
  211. 'signType': pay_info['data']['bankRequest']['SIBGTYPE'], # 小程序和公众号返回的值不是一个
  212. 'package': pay_info['data']['bankRequest']['PACKAGE'],
  213. 'paySign': pay_info['data']['bankRequest']['PAYSIGN']
  214. })
  215. else: # gateway_type == AppPlatformType.JD
  216. response = JsonResponse({
  217. 'result': 1,
  218. 'description': 'SUCCESS',
  219. 'payload': {
  220. 'payUrl': pay_info['data']['bankRequest']['PAY_URL']
  221. }})
  222. task_caller('poll_user_recharge_record',
  223. delay = PollRecordDefine.DELAY_BEFORE,
  224. expires = PollRecordDefine.TASK_EXPIRES,
  225. pay_app_type = self.payment_gateway.pay_app_type,
  226. record_id = str(self.record.id),
  227. interval = PollRecordDefine.WAIT_EACH_ROUND,
  228. total_count = PollRecordDefine.TOTAL_ROUNDS)
  229. return response