wechat.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import traceback
  6. from decimal import Decimal
  7. import xmltodict
  8. from django.conf import settings
  9. from pyexpat import ExpatError
  10. from django.http import HttpResponse
  11. from typing import TYPE_CHECKING, Dict
  12. from apilib.monetary import RMB
  13. from apilib.utils_json import JsonResponse
  14. from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp
  15. from apps.web.constant import PollRecordDefine
  16. from apps.web.core.utils import async_operation
  17. from apps.web.exceptions import UserServerException
  18. from library.wechatpy.constants import WeChatErrorCode
  19. from library.wechatbase.exceptions import WeChatException, WechatValidationError, InvalidSignatureException
  20. import simplejson as json
  21. from apps.web.core.payment.wechat import WechatPaymentGateway
  22. from taskmanager.mediator import task_caller
  23. if TYPE_CHECKING:
  24. from apps.web.common.transaction.pay import RechargeRecordT
  25. logger = logging.getLogger(__name__)
  26. def update_record(record, **payload):
  27. # type: (RechargeRecordT, Dict)->bool
  28. if 'time_end' in payload:
  29. import arrow
  30. finished_time = arrow.get(payload["time_end"], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive
  31. else:
  32. finished_time = datetime.datetime.now()
  33. return record.succeed(wxOrderNo = payload['transaction_id'],
  34. transactionId = payload['transaction_id'],
  35. finishedTime = finished_time,
  36. **{
  37. 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  38. })
  39. class WechatPayRecordPoller(PayRecordPoller):
  40. def update_record(self, record, **payload):
  41. # type: (RechargeRecordT, Dict)->bool
  42. return update_record(record, **payload)
  43. def action_of_pay(self, payment_gateway, record):
  44. # type: (WechatPaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
  45. TradeMap = {
  46. 'SUCCESS': PayNotifyAction.NeedHandle,
  47. 'PAYERROR': PayNotifyAction.NoHandle,
  48. 'NOTPAY': PayNotifyAction.Unknown,
  49. 'CLOSED': PayNotifyAction.NoHandle,
  50. 'REFUND': PayNotifyAction.NoHandle
  51. }
  52. try:
  53. result = payment_gateway.api_trade_query(out_trade_no = record.orderNo)
  54. if result['trade_state'] not in TradeMap:
  55. return PayNotifyAction.Unknown, result
  56. action = TradeMap[result['trade_state']]
  57. return action, result
  58. except Exception as e:
  59. logger.exception(e)
  60. return PayNotifyAction.Unknown, {}
  61. class WechatPayNotifier(PayNotifier):
  62. def __init__(self, request, record_cls_factory):
  63. super(WechatPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
  64. def parse_request(self, request):
  65. return xmltodict.parse(request.body)['xml'] # type: Dict
  66. @property
  67. def out_trade_no(self):
  68. return self.payload['out_trade_no']
  69. def verify(self, payment_gateway, record, payload):
  70. # type: (WechatPaymentGateway, RechargeRecordT, Dict)->None
  71. notifier_money = int(payload['total_fee'])
  72. if record.fen_total_fee != notifier_money:
  73. raise WechatValidationError(errmsg = u"invalid money",
  74. lvalue = str(record.fen_total_fee),
  75. rvalue = str(notifier_money),
  76. client = payment_gateway.client)
  77. def update_record(self, record, **payload):
  78. # type: (RechargeRecordT, Dict)->bool
  79. return update_record(record, **payload)
  80. @classmethod
  81. def reply(cls, msg, ok):
  82. # type:(basestring, bool)->HttpResponse
  83. return HttpResponse(WechatPaymentGateway.reply(msg, ok))
  84. def do(self, post_pay):
  85. # type: (callable)->HttpResponse
  86. try:
  87. raw = self.payload
  88. logger.debug('received wechat pay notify: %s' % json.dumps(raw))
  89. return_code = raw['return_code']
  90. if return_code != 'SUCCESS':
  91. logger.error('return_code = %s' % return_code)
  92. return self.reply('OK', True)
  93. out_trade_no = raw['out_trade_no']
  94. money = RMB(raw['total_fee']) * Decimal('0.01')
  95. wx_order_no = raw['transaction_id']
  96. pay_open_id = raw['openid']
  97. logger.debug(
  98. 'processing payment notify. payOpenId = {}, orderNo = {}, wxOrderNo = {}, money = {}'.format(
  99. pay_open_id, out_trade_no, wx_order_no, money))
  100. # TODO 数据库分片修改
  101. # dealerId = json.loads(raw['attach'])['dealerId']
  102. # record = self.record_cls.get_record(order_no = out_trade_no, dealer_id = dealerId)
  103. record = self.record_cls.get_record(order_no = out_trade_no) # type: RechargeRecordT
  104. if not record:
  105. logger.error('no such record. orderNo = {}'.format(out_trade_no))
  106. return self.reply("OK", True)
  107. if record.is_success:
  108. logger.info('recharge record has done. orderNo = {}'.format(out_trade_no))
  109. return self.reply("OK", True)
  110. payment_gateway = WechatPaymentGateway.from_gateway_key(
  111. record.my_gateway,
  112. record.pay_gateway_key,
  113. record.pay_app_type) # type: WechatPaymentGateway
  114. if not payment_gateway.check(raw):
  115. raise InvalidSignatureException(
  116. errCode = WeChatErrorCode.MY_ERROR_SIGNATURE,
  117. errMsg = u'支付通知签名错误',
  118. client = payment_gateway.client
  119. )
  120. if 'payOpenId' in record.attachParas and record.attachParas['payOpenId'] and \
  121. pay_open_id != record.attachParas['payOpenId']:
  122. raise WechatValidationError(errmsg = 'invalid openid',
  123. lvalue = record.attachParas['payOpenId'],
  124. rvalue = pay_open_id,
  125. client = payment_gateway.client)
  126. if 'payOpenId' in record.extraInfo and record.extraInfo['payOpenId'] and \
  127. pay_open_id != record.extraInfo['payOpenId']:
  128. raise WechatValidationError(errmsg = 'invalid openid',
  129. lvalue = record.extraInfo['payOpenId'],
  130. rvalue = pay_open_id,
  131. client = payment_gateway.client)
  132. self.verify(payment_gateway, record, raw)
  133. result_code = raw['result_code']
  134. if result_code != 'SUCCESS':
  135. try:
  136. errorMsg = u'%s(%s)' % (raw.get('err_code_des', u'微信支付返回错误'), result_code)
  137. logger.debug('%s failure confirmed. message = %s' % (repr(record), errorMsg))
  138. record.fail(wxOrderNo = wx_order_no, finishedTime = datetime.datetime.now(), desciprtion = errorMsg)
  139. return self.reply('OK', True)
  140. except Exception as e:
  141. logger.exception('save recharge record failure. code = %s; error = %s; record = %s' % (
  142. result_code, e, repr(record)))
  143. return self.reply(u'数据记录失败', False)
  144. order_cache_mgr = OrderCacheMgr(record)
  145. try:
  146. if OrderCacheMgr(record).check_and_set_done():
  147. logger.debug('%s has been done because cache in notify.' % repr(record))
  148. return self.reply('OK', True)
  149. except Exception as e:
  150. logger.error(
  151. 'cache key is not exist or exception. key = %s' % order_cache_mgr.key)
  152. modified = self.update_record(record, **raw)
  153. if not modified:
  154. # 如果没有任何记录被更新则说明已经处理了
  155. logger.debug('%s has been done because db in notify' % repr(record))
  156. return self.reply('OK', True)
  157. async_operation(post_pay, record = record)
  158. logger.info('[pay notifier]%s successfully confirmed' % repr(record))
  159. return self.reply('OK', True)
  160. except WeChatException as e:
  161. logger.error(repr(e))
  162. return self.reply('ERROR', False)
  163. except ExpatError as e:
  164. logger.error('parse xml error. may be XXE. e = %s' % (e,))
  165. return WechatPayNotifier.reply('invalid xml', False)
  166. except Exception as e:
  167. logger.exception(e)
  168. return self.reply('UNKNOWN', False)
  169. class WechatPullUp(PayPullUp):
  170. """
  171. 微信支付获取支付参数。可能作为商户和资金池商户
  172. """
  173. def do(self): # type: ()->HttpResponse
  174. OrderCacheMgr(self.record).initial()
  175. try:
  176. data = self.payment_gateway.generate_js_payment_params(
  177. payOpenId = self.openId,
  178. out_trade_no = self.record.orderNo,
  179. notify_url = self.payload['notifyUrl'],
  180. money = self.record.money,
  181. body = self.record.subject,
  182. attach = {'dealerId': self.record.ownerId})
  183. except Exception as e:
  184. logger.exception(e)
  185. self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
  186. raise UserServerException(u'拉起支付发生异常')
  187. else:
  188. logger.debug('wechat payment signature: %s' % str(data))
  189. task_caller('poll_user_recharge_record',
  190. delay = PollRecordDefine.DELAY_BEFORE,
  191. expires = PollRecordDefine.TASK_EXPIRES,
  192. pay_app_type = self.payment_gateway.pay_app_type,
  193. record_id = str(self.record.id),
  194. interval = PollRecordDefine.WAIT_EACH_ROUND,
  195. total_count = PollRecordDefine.TOTAL_ROUNDS)
  196. data.update({
  197. 'outTradeNo': self.record.orderNo, 'adShow': self.payload.get('showAd', False)
  198. })
  199. return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': data})