# -*- 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'系统错误,请重试')