# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import traceback from django.conf import settings from django.http import QueryDict, HttpResponse from typing import TYPE_CHECKING, Dict, Optional from apilib.monetary import RMB from apilib.utils_json import JsonResponse from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp from apps.web.constant import PollRecordDefine from apps.web.core.utils import async_operation from apps.web.exceptions import UserServerException from library.dlb import DlbPayException, DlbErrorCode, DlbValidationError from apps.web.core.payment import PaymentGateway from taskmanager.mediator import task_caller if TYPE_CHECKING: from apps.web.common.transaction.pay import RechargeRecordT from typing import Dict from apps.web.core.payment import PaymentGatewayT from apps.web.core.payment.dlb import DlbPaymentGateway logger = logging.getLogger(__name__) def update_record(record, **payload): # type: (RechargeRecordT, Dict)->bool if 'completeTime' in payload: import arrow finished_time = arrow.get(payload['completeTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive else: finished_time = datetime.datetime.now() return record.succeed(wxOrderNo = payload['orderNum'], transactionId = payload['bankOutTradeNum'], finishedTime = finished_time, **{ 'extraInfo.bankTradeNum': payload['bankTradeNum'], 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) class DlbPayReordPoller(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: (DlbPaymentGateway, RechargeRecordT)->(PayNotifyAction, str) try: result = payment_gateway.api_trade_query(out_trade_no = record.orderNo) if 'status' not in result: return PayNotifyAction.Unknown, result if result['status'] == 'SUCCESS': return PayNotifyAction.NeedHandle, { 'orderNum': result['orderNum'], 'bankTradeNum': result['payRecordList'][0]['bankTradeNum'], 'bankOutTradeNum': result['payRecordList'][0]['bankOutTradeNum'] } if result['status'] == 'FAIL': return PayNotifyAction.NoHandle, result if result['status'] == 'INIT': if len(result['payRecordList']) > 0 and result['payRecordList'][0]['payStatus'] == 'FAIL': return PayNotifyAction.NoHandle, result else: return PayNotifyAction.Unknown, result if result['status'] in ['REFUND', 'CLOSE', 'CANCLE']: return PayNotifyAction.NoHandle, result return PayNotifyAction.Unknown, result except DlbPayException as e: logger.error(str(e)) return PayNotifyAction.Unknown, {} except Exception, e: logger.exception(e) return PayNotifyAction.Unknown, {} class DlbPayNotifier(PayNotifier): def __init__(self, request, record_cls_factory): self.timestamp = request.META.get('HTTP_TIMESTAMP') self.token = request.META.get('HTTP_TOKEN') super(DlbPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory) def parse_request(self, request): return request.GET # type: QueryDict @property def out_trade_no(self): return self.payload['requestNum'] def verify(self, payment_gateway, record, payload): # type: (PaymentGatewayT, RechargeRecordT, Dict)->None notifier_fen = int(RMB(payload['orderAmount']) * 100) if record.fen_total_fee != notifier_fen: raise DlbValidationError( errorMsg = u'invalid orderAmount', lvalue = record.fen_total_fee, rvalue = notifier_fen, client = payment_gateway._app) def update_record(self, record, **payload): # type: (RechargeRecordT, Dict)->bool return update_record(record, **payload) def reply(self): return JsonResponse(data = {}) def verify_sign(self, gateway, data, token): # type: (DlbPaymentGateway, dict, str)->bool return gateway.check(data, token) def do(self, post_pay): # type: (callable)->JsonResponse try: payload = self.payload logger.debug('received dlb pay notify: %s' % str(payload)) if payload['status'] != 'SUCCESS': return self.reply() order_no = payload['requestNum'] record = self.record_cls.get_record(order_no = order_no) # type: Optional[RechargeRecordT] if not record: logger.error('no such record'.format(order_no)) return self.reply() if record.is_success(): logger.error( 'record has been finished.'.format(str(record.id), order_no)) return self.reply() payment_gateway = PaymentGateway.from_gateway_key( record.my_gateway, record.pay_gateway_key, record.pay_app_type) # type: DlbPaymentGateway if not self.verify_sign(gateway = payment_gateway, data = { 'timestamp': self.timestamp }, token = self.token): raise DlbPayException(errorCode = DlbErrorCode.MY_ERROR_SIGNATURE, errorMsg = u'TOKEN校验错误', client = payment_gateway._app) 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, **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() except DlbPayException as e: logger.error(str(e)) return self.reply() except Exception as e: logger.exception(e) return self.reply() class DlbPullUp(PayPullUp): """ 哆啦宝支付获取支付参数。目前只作为商户支付 """ def do(self): # type: ()->HttpResponse OrderCacheMgr(self.record).initial() try: pay_url = self.payment_gateway.create_pay_url( order_no = self.record.orderNo, money = self.record.money, attach = {'dealerId': self.record.ownerId}, notify_url = self.payload.get('notifyUrl'), front_url = self.payload.get('front_url')) except Exception as e: logger.exception(e) self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc()) raise UserServerException(u'拉起支付发生异常') else: if not pay_url: self.record.fail(description = u'调起支付失败,请刷新后重试') raise UserServerException(u'调起支付失败,请刷新后重试') else: 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 JsonResponse({ 'result': 1, 'description': 'SUCCESS', 'payload': { 'payType': self.payment_gateway.pay_app_type, 'payUrl': pay_url }})