123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import logging
- import time
- from django.core.handlers.wsgi import WSGIRequest
- from django.http import HttpResponse
- from django.utils.module_loading import import_string
- from typing import TYPE_CHECKING, cast
- from apilib.systypes import StrEnum, Singleton
- from apps import lockCache
- from apps.web.core import PayAppType
- from apps.web.core.payment import PaymentGateway
- from apps.web.exceptions import UserServerException
- if TYPE_CHECKING:
- from apps.web.dealer.models import DealerRechargeRecord, RefundDealerRechargeRecord
- from apps.web.user.models import RechargeRecord, RefundMoneyRecord
- from typing import Union, Optional, Callable, Dict
- import six
- RechargeRecordT = Union[DealerRechargeRecord, RechargeRecord]
- RefundRecordT = Union[RefundDealerRechargeRecord, RefundMoneyRecord]
- from apps.web.core.payment.type_checking import PaymentGatewayT
- logger = logging.getLogger(__name__)
- class PayNotifyAction(StrEnum):
- NeedHandle = 'needHandle'
- NoHandle = 'noHandle'
- Unknown = 'unknown'
- class OrderCacheMgr(object):
- KEY = '{}-{}-lock'
- def __init__(self, record_or_id, cls_name = None):
- # type: (Union[RechargeRecordT, basestring], Optional[str])->None
- if isinstance(record_or_id, basestring):
- assert cls_name, 'cls_name must not be none if record is string'
- self.key = self.KEY.format(cls_name, record_or_id)
- else:
- assert not cls_name, 'cls_name must be none if record is object'
- self.key = self.KEY.format(record_or_id.__class__.__name__, str(record_or_id.id))
- def initial(self):
- try:
- return lockCache.set(self.key, '0', 25 * 3600)
- except Exception as e:
- logger.exception(e)
- return 0
- def check_and_set_done(self):
- # type: ()->bool
- new_value = lockCache.incr(self.key)
- if int(new_value) >= 2:
- return True
- else:
- return False
- def has_done(self):
- # type: ()->bool
- new_value = lockCache.get(self.key, None)
- if not new_value:
- return False
- if int(new_value) >= 1:
- return True
- else:
- return False
- class PayRecordPoller(object):
- def __init__(self, record_id, interval, total_count, record_cls, post_pay):
- # type: (str, int, int, six.class_types, Callable)->None
- self.record_id = record_id
- self.interval = interval
- self.total_count = total_count
- self.record_cls = record_cls
- self.post_pay = post_pay
- def update_record(self, record, **payload):
- # type: (RechargeRecordT, Dict)->bool
- raise NotImplementedError('sub class must implement update_record')
- def action_of_pay(self, payment_gateway, record):
- # type: (PaymentGateway, RechargeRecordT)->(PayNotifyAction, str)
- raise NotImplementedError('sub class must implement action_of_pay')
- def start(self):
- logger.debug('poll record<id={}> pay status.'.format(self.record_id))
- count = 0
- order_cache_mgr = OrderCacheMgr(self.record_id, self.record_cls.__name__)
- if order_cache_mgr.has_done():
- logger.debug('record<id={}> has done.'.format(self.record_id))
- return
- record = self.record_cls.objects(id = self.record_id).first() # type: RechargeRecordT
- if not record:
- logger.error('record<id={}> is not exist.'.format(self.record_id))
- return
- if record.is_success:
- logger.error('record<id={},order={}> is success.'.format(self.record_id, record.orderNo))
- return
- payment_gateway = PaymentGateway.from_gateway_key(
- record.my_gateway,
- record.pay_gateway_key,
- record.pay_app_type) # type: PaymentGatewayT
- while count < self.total_count:
- try:
- if count != 0 and order_cache_mgr.has_done():
- logger.debug('record<id={},order={}> has done.'.format(self.record_id, record.orderNo))
- return
- action, query_trade_result = self.action_of_pay(payment_gateway, record)
- logger.debug(
- 'record<id={},order={}> trade info: trade result = {}, action = {}.'.format(
- self.record_id, record.orderNo, str(query_trade_result), action))
- if action == PayNotifyAction.NoHandle:
- return
- if action == PayNotifyAction.Unknown:
- continue
- try:
- if order_cache_mgr.check_and_set_done():
- logger.debug(
- 'record<id={},order={}> check has done.'.format(self.record_id, record.orderNo))
- return
- except Exception as e:
- logger.error(
- 'record<id={},order={}> cache key is not exist or exception. exception = {}, key = {}'.format(
- self.record_id, record.orderNo, str(e), order_cache_mgr.key))
- modified = self.update_record(record, **query_trade_result)
- if not modified:
- logger.debug(
- 'record<id={},order={}> has been modified.'.format(self.record_id, record.orderNo))
- return
- return self.post_pay(record)
- except Exception as e:
- logger.exception(
- 'record<id={},order={}> poll trade status failure. e = {}'.format(self.record_id, record.orderNo,
- str(e)))
- finally:
- count = count + 1
- time.sleep(self.interval)
- class PayNotifier(object):
- def __init__(self, request, record_cls_factory):
- # type: (WSGIRequest, callable)->None
- self.payload = self.parse_request(request)
- self.record_cls = record_cls_factory(self.out_trade_no)
- def parse_request(self, request):
- # type: (WSGIRequest)->dict
- raise NotImplementedError('sub class must implement update_record')
- @property
- def out_trade_no(self):
- raise AttributeError('no out_trade_no attr.')
- def update_record(self, record, **payload):
- # type: (RechargeRecordT, Dict)->bool
- raise NotImplementedError('sub class must implement update_record')
- def do(self, post_pay):
- # type: (callable)->HttpResponse
- raise NotImplementedError('sub class must implement do')
- class PayPullUpFactoryIntf(object):
- @classmethod
- def create(cls, payment_gateway, record, **payload):
- # type: (PaymentGatewayT, RechargeRecordT, dict)->PayPullUp
- raise NotImplementedError('sub class must implement create')
- class PayPullUp(object):
- def __init__(self, openId, payment_gateway, record, **payload):
- # type: (basestring, PaymentGatewayT, RechargeRecordT, dict)->None
- self.openId = openId
- self.record = record
- self.payment_gateway = payment_gateway
- self.payload = payload
- @classmethod
- def create(cls, factory_cls, payment_gateway, record, **payload):
- # type: (cast(PayPullUpFactoryIntf, None), PaymentGatewayT, RechargeRecordT, dict)->PayPullUp
- return factory_cls.create(payment_gateway, record, **payload)
- def do(self):
- # type: ()->HttpResponse
- raise NotImplementedError('sub class must implement do')
- class PayManager(Singleton):
- def __init__(self):
- super(PayManager, self).__init__()
- self.map_dict = {
- PayAppType.WECHAT: {
- 'poller': import_string('apps.web.common.transaction.pay.wechat.WechatPayRecordPoller'),
- 'notifier': import_string('apps.web.common.transaction.pay.wechat.WechatPayNotifier'),
- 'pullup': import_string('apps.web.common.transaction.pay.wechat.WechatPullUp'),
- },
- PayAppType.ALIPAY: {
- 'poller': import_string('apps.web.common.transaction.pay.alipay.AliPayRecordPoller'),
- 'notifier': import_string('apps.web.common.transaction.pay.alipay.AliPayNotifier'),
- 'pullup': import_string('apps.web.common.transaction.pay.alipay.AlipayPullUp')
- }
- }
- def get_poller(self, pay_app_type):
- assert pay_app_type in self.map_dict, 'not register pay app type'
- return self.map_dict[pay_app_type]['poller']
- def get_notifier(self, pay_app_type):
- assert pay_app_type in self.map_dict, 'not register pay app type'
- return self.map_dict[pay_app_type]['notifier']
- def get_pull_up(self, pay_app_type):
- if pay_app_type not in self.map_dict:
- raise UserServerException(u'第三方支付配置错误,请联系平台客服(1002)')
- return self.map_dict[pay_app_type]['pullup']
|