123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- # -*- 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}})
|