# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import traceback import simplejson as json from django.conf import settings from django.http import HttpResponse from typing import TYPE_CHECKING, Dict, Optional from apilib.utils_json import JsonResponse from apps.web.common.transaction.pay import PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp from apps.web.constant import AppPlatformType, PollRecordDefine from apps.web.core import PayAppType from apps.web.core.payment import PaymentGateway from apps.web.core.utils import async_operation, JsonOkResponse from apps.web.exceptions import UserServerException from library.ys import YsException from taskmanager.mediator import task_caller if TYPE_CHECKING: from apps.web.common.transaction.pay import RechargeRecordT from apps.web.core.payment.ys import YsPaymentGateway logger = logging.getLogger(__name__) def update_record(record, **payload): # type: (RechargeRecordT, Dict)->bool if 'wxtimeend' in payload: import arrow finished_time = arrow.get(payload['wxtimeend'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive else: finished_time = datetime.datetime.now() return record.succeed(wxOrderNo = payload['wtorderid'], transactionId = payload['wxtransactionid'], finishedTime = finished_time, **{ 'extraInfo.wxopenid': payload['wxopenid'], 'extraInfo.acctype': payload['acctype'] }) class YsReordPoller(PayRecordPoller): def update_record(self, record, **payload): # type: (RechargeRecordT, Dict)->bool return update_record(record, **payload) def action_of_pay(self, payment_gateway, record): # type: (YsPaymentGateway, RechargeRecordT)->(PayNotifyAction, str) ''' 00 - 成功;98 - 未确定;25 - 找不到原笔交易;AA-待支付;其他 - 失败 :param payment_gateway: :param record: :return: ''' try: result = payment_gateway.api_trade_query(out_trade_no = record.orderNo) if 'resultcode' not in result: return PayNotifyAction.Unknown, result if result['resultcode'] == '00': return PayNotifyAction.NeedHandle, result if result['resultcode'] in ['98', 'AA']: return PayNotifyAction.Unknown, result if result['resultcode'] == '25': return PayNotifyAction.NoHandle, result # 其他说明交易失败, 不处理 return PayNotifyAction.NoHandle, result except YsException as e: logger.error(str(e)) return PayNotifyAction.Unknown, {} except Exception, e: logger.exception(e) return PayNotifyAction.Unknown, {} class YsNotifier(PayNotifier): def __init__(self, request, record_cls_factory): super(YsNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory) def parse_request(self, request): return json.loads(request.body) # type: Dict @property def out_trade_no(self): return self.payload['tradetrace'] def verify(self, payment_gateway, record, payload): # type: (YsPaymentGateway, RechargeRecordT, Dict)->None pass def update_record(self, record, **payload): # type: (RechargeRecordT, Dict)->bool return update_record(record, **payload) def reply(self, retry = False): if retry: return JsonResponse({'resultcode': ''}) else: return JsonResponse({'resultcode': '00'}) def verify_sign(self, gateway, data): # type: (YsPaymentGateway, dict)->bool sign = data.pop('sign') return gateway.client.sign(data) == sign def do(self, post_pay): # type: (callable)->JsonResponse try: payload = self.payload logger.debug('received ys pay notify: {}'.format(str(payload))) order_no = payload['tradetrace'] record = self.record_cls.get_record(order_no = order_no) # type: Optional[RechargeRecordT] if not record: logger.error( 'no such record. orderNo = {}'.format(order_no)) return self.reply(True) if record.is_success(): logger.error('record has been finished. orderNo = {}'.format(order_no)) return self.reply(True) payment_gateway = PaymentGateway.from_gateway_key( record.my_gateway, record.pay_gateway_key, record.pay_app_type) # type: YsPaymentGateway # 校验签名 self.verify_sign(payment_gateway, payload) # 校验参数 self.verify(payment_gateway, record, payload) order_cache_mgr = OrderCacheMgr(record) try: if order_cache_mgr.check_and_set_done(): logger.debug('{} has been done because cache in notify.'.format(repr(record))) return self.reply() except Exception as e: logger.error( 'cache key is not exist or exception. exception = {}, key = {}'.format(str(e), order_cache_mgr.key)) modified = self.update_record(record = record, **payload) if not modified: logger.debug('{} has been done because db in notify'.format(repr(record))) return self.reply() logger.info('{} successfully confirmed'.format(repr(record))) async_operation(post_pay, record = record) return self.reply(True) except YsException as e: logger.error(str(e)) return self.reply() except Exception as e: logger.exception(e) return self.reply(retry = True) class YsPullUp(PayPullUp): """ 易生获取支付参数。只用作商户,所以GOLDEN为True """ def do(self): # type: ()->HttpResponse OrderCacheMgr(self.record).initial() try: wtorderid, pay_info = self.payment_gateway.unified_order( out_trade_no = self.record.orderNo, money = self.record.money, notify_url = self.payload['notifyUrl'], subject = self.record.subject, openId = self.openId) except Exception as e: logger.exception(e) self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc()) raise UserServerException(u'拉起支付发生异常') else: logger.debug("wt order id is {}. pay info = {}".format(wtorderid, pay_info)) response = None if self.payment_gateway.gateway_type == AppPlatformType.ALIPAY: response = JsonOkResponse(payload = { 'tradeNO': pay_info['tradeNO'], 'outTradeNo': self.record.orderNo, 'adShow': self.payload.get('showAd', False) }) elif self.payment_gateway.gateway_type == AppPlatformType.WECHAT: pay_info.update({ 'outTradeNo': self.record.orderNo, 'golden': True, 'adShow': self.payload.get('showAd', False) }) response = JsonOkResponse(payload = pay_info) if not response: self.record.fail(description = u'不支持该支付类型') raise UserServerException(u'不支持该支付类型') task_caller('poll_user_recharge_record', delay = PollRecordDefine.DELAY_BEFORE, expires = PollRecordDefine.TASK_EXPIRES, pay_app_type = self.payment_gateway.pay_app_type, record_id = str(self.record.id), interval = PollRecordDefine.WAIT_EACH_ROUND, total_count = PollRecordDefine.TOTAL_ROUNDS) return response