123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import logging
- import traceback
- from django.conf import settings
- from django.http import HttpResponse
- from typing import TYPE_CHECKING, Dict
- import simplejson as json
- from apilib.monetary import RMB
- from apps.web.common.errors import DuplicatedRefundRecordFound
- from apps.web.common.transaction.pay import PayNotifyAction, PayRecordPoller, OrderCacheMgr, PayNotifier, PayPullUp
- from apps.web.constant import PollRecordDefine
- from apps.web.core import ROLE
- from apps.web.core.payment import PaymentGateway, WithdrawGateway
- from apps.web.core.utils import async_operation, JsonOkResponse
- from apps.web.exceptions import UserServerException
- from apps.web.utils import get_alipay_gateway_result
- from library.alipay import AliValidationError, AliException, AliErrorCode
- from taskmanager.mediator import task_caller
- if TYPE_CHECKING:
- from apps.web.core.payment.type_checking import PaymentGatewayT
- from apps.web.core.payment.ali import AliPayGateway, AliPayWithdrawGateway
- from apps.web.common.transaction.pay import RechargeRecordT
- from typing import Dict
- from apps.web.common.models import WithdrawRecord, CapitalUser
- logger = logging.getLogger(__name__)
- def update_record(record, **payload):
- # type: (RechargeRecordT, Dict)->bool
- import arrow
- if 'send_pay_date' in payload:
- finished_time = arrow.get(payload['send_pay_date'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
- elif 'gmt_payment' in payload:
- finished_time = arrow.get(payload['gmt_payment'], 'YYYY-MM-DD HH:mm:ss', tzinfo = settings.TIME_ZONE).naive
- else:
- finished_time = datetime.datetime.now()
- if 'notify_id' in payload:
- return record.succeed(wxOrderNo = payload['trade_no'],
- transactionId = payload['trade_no'],
- finishedTime = finished_time,
- **{
- 'extraInfo.notify_id': payload.get('notify_id'),
- 'extraInfo.notify_time': payload.get('notify_time'),
- 'extraInfo.notify_type': payload.get('notify_type')
- })
- else:
- return record.succeed(wxOrderNo = payload['trade_no'],
- transactionId = payload['trade_no'],
- finishedTime = finished_time,
- **{
- 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- })
- class AliPayRecordPoller(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: (AliPayGateway, RechargeRecordT)->(PayNotifyAction, str)
- try:
- result = payment_gateway.api_trade_query(record.orderNo)
- trade_status = result['trade_status']
- if trade_status == 'WAIT_BUYER_PAY':
- return PayNotifyAction.Unknown, result
- if trade_status == 'TRADE_CLOSED':
- return PayNotifyAction.NoHandle, result
- if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
- if 'trade_no' not in result or not result['trade_no']:
- return PayNotifyAction.Unknown, result
- else:
- return PayNotifyAction.NeedHandle, result
- return PayNotifyAction.Unknown, result
- except Exception as e:
- logger.exception(e)
- return PayNotifyAction.Unknown, {}
- class AliPayNotifier(PayNotifier):
- def __init__(self, request, record_cls_factory):
- super(AliPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory)
- def parse_request(self, request):
- return request.POST.dict() # type: Dict
- @property
- def out_trade_no(self):
- return self.payload['out_trade_no']
- def verify(self, gateway, record, payload):
- # type: (PaymentGatewayT, RechargeRecordT, Dict)->None
- notifier_fen = int(RMB(payload['total_amount']) * 100)
- if notifier_fen != record.fen_total_fee:
- raise AliValidationError(
- errmsg = u'total_amount校验失败',
- lvalue = record.fen_total_fee,
- rvalue = notifier_fen,
- client = gateway.client)
- def update_record(self, record, **payload):
- # type: (RechargeRecordT, Dict)->bool
- return update_record(record, **payload)
- @classmethod
- def reply(cls, msg):
- return HttpResponse(msg)
- def do(self, post_pay):
- # type: (callable)->HttpResponse
- try:
- payload = self.payload
- logger.info('received ali pay notify: %s' % str(payload))
- order_no = payload['out_trade_no']
- record = self.record_cls.get_record(order_no = order_no) # type: RechargeRecordT
- if record is None:
- logger.error(
- 'no such record. orderNo = {}'.format(order_no))
- return HttpResponse('success')
- if record.is_success:
- logger.error('record has been finished. orderNo = {}'.format(order_no))
- return HttpResponse('success')
- gateway = PaymentGateway.from_gateway_key(
- record.my_gateway,
- record.pay_gateway_key,
- record.pay_app_type) # type: AliPayGateway
- if payload['app_id'] != gateway.appid:
- raise AliValidationError(
- errmsg = u'appid校验失败',
- lvalue = gateway.appid,
- rvalue = payload['app_id'],
- client = gateway.client)
- if not gateway.check(payload):
- raise AliException(
- errCode = AliErrorCode.MY_ERROR_SIGNATURE,
- errMsg = u'签名错误',
- client = gateway.client)
- if 'payOpenId' in record.attachParas and record.attachParas['payOpenId'] and payload['buyer_id'] != \
- record.attachParas['payOpenId']:
- raise AliValidationError(
- errmsg = u'buyer_id校验失败',
- lvalue = record.attachParas['payOpenId'],
- rvalue = payload['buyer_id'],
- client = gateway.client)
- if 'payOpenId' in record.extraInfo and record.extraInfo['payOpenId'] and payload['buyer_id'] != \
- record.extraInfo['payOpenId']:
- raise AliValidationError(
- errmsg = u'buyer_id校验失败',
- lvalue = record.extraInfo['payOpenId'],
- rvalue = payload['buyer_id'],
- client = gateway.client)
- self.verify(gateway, record, payload)
- # 只有TRADE_SUCCESS和TRADE_FINISHED才是成功状态
- if ('trade_status' not in payload) or (payload['trade_status'] not in ['TRADE_FINISHED', 'TRADE_SUCCESS']):
- return HttpResponse('UNFINISHED')
- order_cache_mgr = OrderCacheMgr(record)
- try:
- if order_cache_mgr.check_and_set_done():
- logger.debug('%s has been done because cache in notify.' % repr(record))
- return HttpResponse('success')
- except Exception as e:
- logger.error(
- 'cache key is not exist or exception. key = %s' % order_cache_mgr.key)
- modified = self.update_record(record, **payload)
- if not modified:
- logger.error('recharge record has done. orderNo = %s' % order_no)
- return HttpResponse('success')
- logger.info('%s successfully confirmed' % repr(record))
- async_operation(post_pay, record = record)
- return HttpResponse('success')
- except Exception as e:
- logger.exception(e)
- return HttpResponse('ERROR')
- class AliPayWithdrawNotifier(object):
- def __init__(self, record_cls):
- self.record_cls = record_cls # type: WithdrawRecord
- def verify(self, gateway, record, payload):
- # type: (WithdrawGateway, WithdrawRecord, Dict)->None
- if str(record.actualPay) != str(payload['trans_amount']):
- raise AliValidationError(
- errmsg = u'total_amount校验失败',
- lvalue = str(record.actualPay),
- rvalue = str(payload['trans_amount']),
- client = gateway.client)
- @classmethod
- def reply(cls, msg):
- return HttpResponse(msg)
- def do(self, payload):
- # type: (dict)->HttpResponse
- try:
- order_no = payload['biz_content']['out_biz_no']
- status = payload['biz_content']['status']
- if status == 'DEALING':
- logger.debug('withdraw record is dealing. ignore. orderNo = {}'.format(order_no))
- return self.reply('success')
- record = self.record_cls.get_record(order_no = order_no) # type: WithdrawRecord
- if record is None:
- logger.warn(
- 'no such record. orderNo = {}'.format(order_no))
- return self.reply('success')
- if record.finished:
- logger.error('record has been finished. orderNo = {}'.format(order_no))
- return self.reply('success')
- gateway = WithdrawGateway.from_withdraw_gateway_key(
- record.withdrawGatewayKey, record.extras.get('gateway_version', 'v1')) # type: AliPayWithdrawGateway
- if payload['app_id'] != gateway.appid:
- raise AliValidationError(
- errmsg = u'appid校验失败',
- lvalue = gateway.appid,
- rvalue = payload['app_id'],
- client = gateway.client)
- if not gateway.check(payload):
- raise AliException(
- errCode = AliErrorCode.MY_ERROR_SIGNATURE,
- errMsg = u'签名错误',
- client = gateway.client)
- self.verify(gateway, record, payload)
- order_cache_mgr = OrderCacheMgr(record)
- try:
- if order_cache_mgr.check_and_set_done():
- logger.debug('%s has been done because cache in notify.' % repr(record))
- return self.reply('success')
- except Exception as e:
- logger.error(
- 'cache key is not exist or exception. key = %s' % order_cache_mgr.key)
- logger.info('%s successfully confirmed' % repr(record))
- payee = ROLE.from_role_id(record.role, record.ownerId) # type: CapitalUser
- if not payee:
- logger.warn('owner id is None. orderNo = {}'.format(order_no))
- return self.reply('success')
- handler = payee.new_withdraw_handler(record)
- if status == 'SUCCESS':
- logger.debug('withdraw record is success. orderNo = {}'.format(order_no))
- if 'pay_fund_order_id' not in payload['biz_content'] or not payload['biz_content']['pay_fund_order_id']:
- logger.error('no pay_fund_order_id. orderNo = {}'.format(order_no))
- return self.reply('success')
- if 'order_id' not in payload['biz_content'] or not payload['biz_content']['order_id']:
- logger.error('no order_id. orderNo = {}'.format(order_no))
- return self.reply('success')
- if 'pay_date' not in payload['biz_content'] or not payload['biz_content']['pay_date']:
- logger.error('has no pay_date. orderNo = {}'.format(order_no))
- return self.reply('success')
- # SUCCESS有可能会变成REFUND. 把状态设置为银行处理, 如果两天后没有退票, 在认为转账成功
- handler.bank_processing(**{
- 'order_id': payload['biz_content']['order_id'],
- 'pay_fund_order_id': payload['biz_content']['pay_fund_order_id'],
- 'pay_date': payload['biz_content']['pay_date']
- })
- return self.reply('success')
- elif status == 'REFUND':
- try:
- handler.revoke(remarks = u'银行退单', description = u'银行退单')
- except DuplicatedRefundRecordFound as e:
- pass
- return self.reply('success')
- elif status == 'FAIL':
- error_code = payload.get('error_code', 'FAIL')
- fail_reason = payload.get('fail_reason', u'提现失败')
- error_msg = '{}({})'.format(fail_reason, error_code)
- handler.revoke(remarks = error_msg, description = error_msg)
- return self.reply('success')
- else:
- logger.warn('unsupport status = {}'.format(status))
- return self.reply('success')
- except Exception as e:
- logger.exception(e)
- return self.reply('fail')
- class AlipayPullUp(PayPullUp):
- """
- 支付宝支付获取支付参数。目前只作为资金池支付
- """
- def do(self): # type: ()->HttpResponse
- OrderCacheMgr(self.record).initial()
- try:
- result = get_alipay_gateway_result(
- gateway = self.payment_gateway,
- out_trade_no = self.record.orderNo,
- money = self.record.money,
- subject = self.record.subject,
- buyer_id = self.openId,
- body = json.dumps({'dealerId': self.record.ownerId}),
- notify_url = self.payload.get('notifyUrl'))
- except Exception as e:
- logger.exception(e)
- self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc())
- raise UserServerException(u'拉起支付发生异常')
- else:
- if result['code'] == u'10000':
- 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 JsonOkResponse(
- payload = {
- 'tradeNO': result['trade_no'],
- 'outTradeNo': self.record.orderNo,
- 'adShow': self.payload.get('showAd', False)
- })
- else:
- logger.error('{openid} is recharging via({pay_app_type}) with fault. result = {result}'.format(
- openid = self.openId, pay_app_type = self.payment_gateway.pay_app_type, result = str(result)))
- self.record.fail(description = result['code'])
- raise UserServerException(u'系统错误,请重试')
|