# -*- 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 PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp from apps.web.constant import PollRecordDefine from apps.web.core.payment import PaymentGateway from apps.web.core.utils import async_operation from apps.web.exceptions import UserServerException from library.saobei import SaobeiException, SaobeiValidationError from taskmanager.mediator import task_caller if TYPE_CHECKING: from apps.web.common.transaction.pay import RechargeRecordT from apps.web.core.payment.saobei import SaobeiPaymentGateway logger = logging.getLogger(__name__) def update_record(record, **payload): # type: (RechargeRecordT, Dict)->bool if 'pay_time' in payload: import arrow finished_time = arrow.get(payload['pay_time'], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive else: finished_time = datetime.datetime.now() return record.succeed(wxOrderNo = payload['out_trade_no'], transactionId = payload['channel_trade_no'], finishedTime = finished_time, **{ 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) class SaobeiPayRecordPoller(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: (SaobeiPaymentGateway, RechargeRecordT)->(PayNotifyAction, str) try: pay_time = record.extraInfo.get('payTime') or record.attachParas.get('payTime') result = payment_gateway.api_trade_query(pay_trace = record.orderNo, pay_time = pay_time) if result['result_code'] == '01': # 成功 return PayNotifyAction.NeedHandle, result if result['result_code'] == '02': # 失败 return PayNotifyAction.NoHandle, {} return PayNotifyAction.Unknown, result except SaobeiException as e: logger.error(str(e)) return PayNotifyAction.Unknown, {} except Exception as e: logger.exception(e) return PayNotifyAction.Unknown, {} class SaobeiPayNotifier(PayNotifier): def __init__(self, request, record_cls_factory): super(SaobeiPayNotifier, 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['terminal_trace'] def verify(self, payment_gateway, record, payload): # type: (SaobeiPaymentGateway, RechargeRecordT, Dict)->None if record.fen_total_fee != int(payload['total_fee']): raise SaobeiValidationError(errmsg = u'无效的total_fee', lvalue = record.fen_total_fee, rvalue = payload['total_fee'], client = payment_gateway.client) def update_record(self, record, **payload): # type: (RechargeRecordT, Dict)->bool return update_record(record, **payload) @classmethod def reply(cls, ok, msg = None): return_code = '01' if ok else '02' if ok: return_msg = 'success' else: return_msg = msg if msg else 'unknown' return JsonResponse(data = {'return_code': return_code, 'return_msg': return_msg}) def verify_sign(self, gateway, data): # type: (SaobeiPaymentGateway, dict)->bool from collections import OrderedDict sign_data = OrderedDict() sign_data['return_code'] = data['return_code'] sign_data['return_msg'] = data['return_msg'] sign_data['result_code'] = data['result_code'] sign_data['pay_type'] = data['pay_type'] sign_data['user_id'] = data['user_id'] sign_data['merchant_name'] = data['merchant_name'] sign_data['merchant_no'] = data['merchant_no'] sign_data['terminal_id'] = data['terminal_id'] sign_data['terminal_trace'] = data['terminal_trace'] sign_data['terminal_time'] = data['terminal_time'] sign_data['total_fee'] = data['total_fee'] sign_data['end_time'] = data['end_time'] sign_data['out_trade_no'] = data['out_trade_no'] sign_data['channel_trade_no'] = data['channel_trade_no'] sign_data['attach'] = data['attach'] sign_data['key_sign'] = data['key_sign'] return gateway.check(sign_data, order = False) def do(self, post_pay): # type: (callable)->JsonResponse try: payload = self.payload logger.debug('received saobei pay notify: {}'.format(str(payload))) if payload['return_code'] != '01': logger.error('return_code = {}, return_msg = {}'.format(payload['return_code'], payload['return_msg'])) return self.reply(False, 'return_code != 01') order_no = payload['terminal_trace'] 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: SaobeiPaymentGateway # 校验签名 self.verify_sign(payment_gateway, payload) # 校验参数 self.verify(payment_gateway, record, payload) if payload['result_code'] != '01': record.fail(wxOrderNo = payload['channel_trade_no'], finishedTime = datetime.datetime.now(), description = payload['return_msg']) return self.reply(True) 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(True) 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(True) logger.info('{} successfully confirmed'.format(repr(record))) async_operation(post_pay, record = record) return self.reply(True) except SaobeiException as e: logger.error(str(e)) return self.reply(True) except Exception as e: logger.exception(e) return self.reply(False, 'EXCEPTION') class SaobeiPullUp(PayPullUp): """ 扫呗支付获取支付参数。目前只作为商户支付 """ def do(self): # type: ()->HttpResponse OrderCacheMgr(self.record).initial() try: pay_url = self.payment_gateway.create_pay_url( pay_trace = self.record.orderNo, pay_time = self.record.extraInfo['payTime'], money = self.record.money, attach = {'dealerId': self.record.ownerId}, body = self.record.subject, notify_url = self.payload['notifyUrl'], front_url = self.payload['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': {'payUrl': pay_url}})