alipay.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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 HttpResponse
  8. from typing import TYPE_CHECKING, Dict
  9. import simplejson as json
  10. from apilib.monetary import RMB
  11. from apps.web.common.errors import DuplicatedRefundRecordFound
  12. from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp
  13. from apps.web.constant import PollRecordDefine
  14. from apps.web.core import ROLE
  15. from apps.web.core.payment import PaymentGateway, WithdrawGateway
  16. from apps.web.core.utils import async_operation, JsonOkResponse
  17. from apps.web.exceptions import UserServerException
  18. from apps.web.utils import get_alipay_gateway_result
  19. from library.alipay import AliValidationError, AliException, AliErrorCode
  20. from taskmanager.mediator import task_caller
  21. if TYPE_CHECKING:
  22. from apps.web.core.payment.type_checking import PaymentGatewayT
  23. from apps.web.core.payment.ali import AliPayGateway, AliPayWithdrawGateway
  24. from apps.web.common.transaction.pay import RechargeRecordT
  25. from typing import Dict
  26. from apps.web.common.models import WithdrawRecord, CapitalUser
  27. logger = logging.getLogger(__name__)
  28. def update_record(record, **payload):
  29. # type: (RechargeRecordT, Dict)->bool
  30. import arrow
  31. if 'send_pay_date' in payload:
  32. finished_time = arrow.get(payload['send_pay_date'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
  33. elif 'gmt_payment' in payload:
  34. finished_time = arrow.get(payload['gmt_payment'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
  35. else:
  36. finished_time = datetime.datetime.now()
  37. if 'notify_id' in payload:
  38. return record.succeed(wxOrderNo = payload['trade_no'],
  39. transactionId = payload['trade_no'],
  40. finishedTime = finished_time,
  41. **{
  42. 'extraInfo.notify_id': payload.get('notify_id'),
  43. 'extraInfo.notify_time': payload.get('notify_time'),
  44. 'extraInfo.notify_type': payload.get('notify_type')
  45. })
  46. else:
  47. return record.succeed(wxOrderNo = payload['trade_no'],
  48. transactionId = payload['trade_no'],
  49. finishedTime = finished_time,
  50. **{
  51. 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  52. })
  53. class AliPayRecordPoller(PayRecordPoller):
  54. def update_record(self, record, **payload):
  55. # type: (RechargeRecordT, Dict)->bool
  56. return update_record(record, **payload)
  57. def action_of_pay(self, payment_gateway, record):
  58. # type: (AliPayGateway, RechargeRecordT)->(PayNotifyAction, str)
  59. try:
  60. result = payment_gateway.api_trade_query(record.orderNo)
  61. trade_status = result['trade_status']
  62. if trade_status == 'WAIT_BUYER_PAY':
  63. return PayNotifyAction.Unknown, result
  64. if trade_status == 'TRADE_CLOSED':
  65. return PayNotifyAction.NoHandle, result
  66. if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
  67. if 'trade_no' not in result or not result['trade_no']:
  68. return PayNotifyAction.Unknown, result
  69. else:
  70. return PayNotifyAction.NeedHandle, result
  71. return PayNotifyAction.Unknown, result
  72. except Exception as e:
  73. logger.exception(e)
  74. return PayNotifyAction.Unknown, {}
  75. class AliPayNotifier(PayNotifier):
  76. def __init__(self, request, record_cls_factory):
  77. super(AliPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
  78. def parse_request(self, request):
  79. return request.POST.dict() # type: Dict
  80. @property
  81. def out_trade_no(self):
  82. return self.payload['out_trade_no']
  83. def verify(self, gateway, record, payload):
  84. # type: (PaymentGatewayT, RechargeRecordT, Dict)->None
  85. notifier_fen = int(RMB(payload['total_amount']) * 100)
  86. if notifier_fen != record.fen_total_fee:
  87. raise AliValidationError(
  88. errmsg = u'total_amount校验失败',
  89. lvalue = record.fen_total_fee,
  90. rvalue = notifier_fen,
  91. client = gateway.client)
  92. def update_record(self, record, **payload):
  93. # type: (RechargeRecordT, Dict)->bool
  94. return update_record(record, **payload)
  95. @classmethod
  96. def reply(cls, msg):
  97. return HttpResponse(msg)
  98. def do(self, post_pay):
  99. # type: (callable)->HttpResponse
  100. try:
  101. payload = self.payload
  102. logger.info('received ali pay notify: %s' % str(payload))
  103. order_no = payload['out_trade_no']
  104. record = self.record_cls.get_record(order_no = order_no) # type: RechargeRecordT
  105. if record is None:
  106. logger.error(
  107. 'no such record. orderNo = {}'.format(order_no))
  108. return HttpResponse('success')
  109. if record.is_success:
  110. logger.error('record has been finished. orderNo = {}'.format(order_no))
  111. return HttpResponse('success')
  112. gateway = PaymentGateway.from_gateway_key(
  113. record.my_gateway,
  114. record.pay_gateway_key,
  115. record.pay_app_type) # type: AliPayGateway
  116. if payload['app_id'] != gateway.appid:
  117. raise AliValidationError(
  118. errmsg = u'appid校验失败',
  119. lvalue = gateway.appid,
  120. rvalue = payload['app_id'],
  121. client = gateway.client)
  122. if not gateway.check(payload):
  123. raise AliException(
  124. errCode = AliErrorCode.MY_ERROR_SIGNATURE,
  125. errMsg = u'签名错误',
  126. client = gateway.client)
  127. if 'payOpenId' in record.attachParas and record.attachParas['payOpenId'] and payload['buyer_id'] != \
  128. record.attachParas['payOpenId']:
  129. raise AliValidationError(
  130. errmsg = u'buyer_id校验失败',
  131. lvalue = record.attachParas['payOpenId'],
  132. rvalue = payload['buyer_id'],
  133. client = gateway.client)
  134. if 'payOpenId' in record.extraInfo and record.extraInfo['payOpenId'] and payload['buyer_id'] != \
  135. record.extraInfo['payOpenId']:
  136. raise AliValidationError(
  137. errmsg = u'buyer_id校验失败',
  138. lvalue = record.extraInfo['payOpenId'],
  139. rvalue = payload['buyer_id'],
  140. client = gateway.client)
  141. self.verify(gateway, record, payload)
  142. # 只有TRADE_SUCCESS和TRADE_FINISHED才是成功状态
  143. if ('trade_status' not in payload) or (payload['trade_status'] not in ['TRADE_FINISHED', 'TRADE_SUCCESS']):
  144. return HttpResponse('UNFINISHED')
  145. order_cache_mgr = OrderCacheMgr(record)
  146. try:
  147. if order_cache_mgr.check_and_set_done():
  148. logger.debug('%s has been done because cache in notify.' % repr(record))
  149. return HttpResponse('success')
  150. except Exception as e:
  151. logger.error(
  152. 'cache key is not exist or exception. key = %s' % order_cache_mgr.key)
  153. modified = self.update_record(record, **payload)
  154. if not modified:
  155. logger.error('recharge record has done. orderNo = %s' % order_no)
  156. return HttpResponse('success')
  157. logger.info('%s successfully confirmed' % repr(record))
  158. async_operation(post_pay, record = record)
  159. return HttpResponse('success')
  160. except Exception as e:
  161. logger.exception(e)
  162. return HttpResponse('ERROR')
  163. class AliPayWithdrawNotifier(object):
  164. def __init__(self, record_cls):
  165. self.record_cls = record_cls # type: WithdrawRecord
  166. def verify(self, gateway, record, payload):
  167. # type: (WithdrawGateway, WithdrawRecord, Dict)->None
  168. if str(record.actualPay) != str(payload['trans_amount']):
  169. raise AliValidationError(
  170. errmsg = u'total_amount校验失败',
  171. lvalue = str(record.actualPay),
  172. rvalue = str(payload['trans_amount']),
  173. client = gateway.client)
  174. @classmethod
  175. def reply(cls, msg):
  176. return HttpResponse(msg)
  177. def do(self, payload):
  178. # type: (dict)->HttpResponse
  179. try:
  180. order_no = payload['biz_content']['out_biz_no']
  181. status = payload['biz_content']['status']
  182. if status == 'DEALING':
  183. logger.debug('withdraw record is dealing. ignore. orderNo = {}'.format(order_no))
  184. return self.reply('success')
  185. record = self.record_cls.get_record(order_no = order_no) # type: WithdrawRecord
  186. if record is None:
  187. logger.warn(
  188. 'no such record. orderNo = {}'.format(order_no))
  189. return self.reply('success')
  190. if record.finished:
  191. logger.error('record has been finished. orderNo = {}'.format(order_no))
  192. return self.reply('success')
  193. gateway = WithdrawGateway.from_withdraw_gateway_key(
  194. record.withdrawGatewayKey, record.extras.get('gateway_version', 'v1')) # type: AliPayWithdrawGateway
  195. if payload['app_id'] != gateway.appid:
  196. raise AliValidationError(
  197. errmsg = u'appid校验失败',
  198. lvalue = gateway.appid,
  199. rvalue = payload['app_id'],
  200. client = gateway.client)
  201. if not gateway.check(payload):
  202. raise AliException(
  203. errCode = AliErrorCode.MY_ERROR_SIGNATURE,
  204. errMsg = u'签名错误',
  205. client = gateway.client)
  206. self.verify(gateway, record, payload)
  207. order_cache_mgr = OrderCacheMgr(record)
  208. try:
  209. if order_cache_mgr.check_and_set_done():
  210. logger.debug('%s has been done because cache in notify.' % repr(record))
  211. return self.reply('success')
  212. except Exception as e:
  213. logger.error(
  214. 'cache key is not exist or exception. key = %s' % order_cache_mgr.key)
  215. logger.info('%s successfully confirmed' % repr(record))
  216. payee = ROLE.from_role_id(record.role, record.ownerId) # type: CapitalUser
  217. if not payee:
  218. logger.warn('owner id is None. orderNo = {}'.format(order_no))
  219. return self.reply('success')
  220. handler = payee.new_withdraw_handler(record)
  221. if status == 'SUCCESS':
  222. logger.debug('withdraw record is success. orderNo = {}'.format(order_no))
  223. if 'pay_fund_order_id' not in payload['biz_content'] or not payload['biz_content']['pay_fund_order_id']:
  224. logger.error('no pay_fund_order_id. orderNo = {}'.format(order_no))
  225. return self.reply('success')
  226. if 'order_id' not in payload['biz_content'] or not payload['biz_content']['order_id']:
  227. logger.error('no order_id. orderNo = {}'.format(order_no))
  228. return self.reply('success')
  229. if 'pay_date' not in payload['biz_content'] or not payload['biz_content']['pay_date']:
  230. logger.error('has no pay_date. orderNo = {}'.format(order_no))
  231. return self.reply('success')
  232. # SUCCESS有可能会变成REFUND. 把状态设置为银行处理, 如果两天后没有退票, 在认为转账成功
  233. handler.bank_processing(**{
  234. 'order_id': payload['biz_content']['order_id'],
  235. 'pay_fund_order_id': payload['biz_content']['pay_fund_order_id'],
  236. 'pay_date': payload['biz_content']['pay_date']
  237. })
  238. return self.reply('success')
  239. elif status == 'REFUND':
  240. try:
  241. handler.revoke(remarks = u'银行退单', description = u'银行退单')
  242. except DuplicatedRefundRecordFound as e:
  243. pass
  244. return self.reply('success')
  245. elif status == 'FAIL':
  246. error_code = payload.get('error_code', 'FAIL')
  247. fail_reason = payload.get('fail_reason', u'提现失败')
  248. error_msg = '{}({})'.format(fail_reason, error_code)
  249. handler.revoke(remarks = error_msg, description = error_msg)
  250. return self.reply('success')
  251. else:
  252. logger.warn('unsupport status = {}'.format(status))
  253. return self.reply('success')
  254. except Exception as e:
  255. logger.exception(e)
  256. return self.reply('fail')
  257. class AlipayPullUp(PayPullUp):
  258. """
  259. 支付宝支付获取支付参数。目前只作为资金池支付
  260. """
  261. def do(self): # type: ()->HttpResponse
  262. OrderCacheMgr(self.record).initial()
  263. try:
  264. result = get_alipay_gateway_result(
  265. gateway = self.payment_gateway,
  266. out_trade_no = self.record.orderNo,
  267. money = self.record.money,
  268. subject = self.record.subject,
  269. buyer_id = self.openId,
  270. body = json.dumps({'dealerId': self.record.ownerId}),
  271. notify_url = self.payload.get('notifyUrl'))
  272. except Exception as e:
  273. logger.exception(e)
  274. self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
  275. raise UserServerException(u'拉起支付发生异常')
  276. else:
  277. if result['code'] == u'10000':
  278. task_caller('poll_user_recharge_record',
  279. delay = PollRecordDefine.DELAY_BEFORE,
  280. expires = PollRecordDefine.TASK_EXPIRES,
  281. pay_app_type = self.payment_gateway.pay_app_type,
  282. record_id = str(self.record.id),
  283. interval = PollRecordDefine.WAIT_EACH_ROUND,
  284. total_count = PollRecordDefine.TOTAL_ROUNDS)
  285. return JsonOkResponse(
  286. payload = {
  287. 'tradeNO': result['trade_no'],
  288. 'outTradeNo': self.record.orderNo,
  289. 'adShow': self.payload.get('showAd', False)
  290. })
  291. else:
  292. logger.error('{openid} is recharging via({pay_app_type}) with fault. result = {result}'.format(
  293. openid = self.openId, pay_app_type = self.payment_gateway.pay_app_type, result = str(result)))
  294. self.record.fail(description = result['code'])
  295. raise UserServerException(u'系统错误,请重试')