# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import traceback from decimal import Decimal import xmltodict from django.conf import settings from pyexpat import ExpatError from django.http import HttpResponse from typing import TYPE_CHECKING, Dict from apilib.monetary import RMB 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.utils import async_operation from apps.web.exceptions import UserServerException from library.wechatpy.constants import WeChatErrorCode from library.wechatbase.exceptions import WeChatException, WechatValidationError, InvalidSignatureException import simplejson as json from apps.web.core.payment.wechat import WechatPaymentGateway from taskmanager.mediator import task_caller if TYPE_CHECKING: from apps.web.common.transaction.pay import RechargeRecordT logger = logging.getLogger(__name__) def update_record(record, **payload): # type: (RechargeRecordT, Dict)->bool if 'time_end' in payload: import arrow finished_time = arrow.get(payload["time_end"], 'YYYYMMDDHHmmss', tzinfo = settings.TIME_ZONE).naive else: finished_time = datetime.datetime.now() return record.succeed(wxOrderNo = payload['transaction_id'], transactionId = payload['transaction_id'], finishedTime = finished_time, **{ 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') }) class WechatPayRecordPoller(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: (WechatPaymentGateway, RechargeRecordT)->(PayNotifyAction, str) TradeMap = { 'SUCCESS': PayNotifyAction.NeedHandle, 'PAYERROR': PayNotifyAction.NoHandle, 'NOTPAY': PayNotifyAction.Unknown, 'CLOSED': PayNotifyAction.NoHandle, 'REFUND': PayNotifyAction.NoHandle } try: result = payment_gateway.api_trade_query(out_trade_no = record.orderNo) if result['trade_state'] not in TradeMap: return PayNotifyAction.Unknown, result action = TradeMap[result['trade_state']] return action, result except Exception as e: logger.exception(e) return PayNotifyAction.Unknown, {} class WechatPayNotifier(PayNotifier): def __init__(self, request, record_cls_factory): super(WechatPayNotifier, self).__init__(request = request, record_cls_factory = record_cls_factory) def parse_request(self, request): return xmltodict.parse(request.body)['xml'] # type: Dict @property def out_trade_no(self): return self.payload['out_trade_no'] def verify(self, payment_gateway, record, payload): # type: (WechatPaymentGateway, RechargeRecordT, Dict)->None notifier_money = int(payload['total_fee']) if record.fen_total_fee != notifier_money: raise WechatValidationError(errmsg = u"invalid money", lvalue = str(record.fen_total_fee), rvalue = str(notifier_money), client = payment_gateway.client) def update_record(self, record, **payload): # type: (RechargeRecordT, Dict)->bool return update_record(record, **payload) @classmethod def reply(cls, msg, ok): # type:(basestring, bool)->HttpResponse return HttpResponse(WechatPaymentGateway.reply(msg, ok)) def do(self, post_pay): # type: (callable)->HttpResponse try: raw = self.payload logger.debug('received wechat pay notify: %s' % json.dumps(raw)) return_code = raw['return_code'] if return_code != 'SUCCESS': logger.error('return_code = %s' % return_code) return self.reply('OK', True) out_trade_no = raw['out_trade_no'] money = RMB(raw['total_fee']) * Decimal('0.01') wx_order_no = raw['transaction_id'] pay_open_id = raw['openid'] logger.debug( 'processing payment notify. payOpenId = {}, orderNo = {}, wxOrderNo = {}, money = {}'.format( pay_open_id, out_trade_no, wx_order_no, money)) # TODO 数据库分片修改 # dealerId = json.loads(raw['attach'])['dealerId'] # record = self.record_cls.get_record(order_no = out_trade_no, dealer_id = dealerId) record = self.record_cls.get_record(order_no = out_trade_no) # type: RechargeRecordT if not record: logger.error('no such record. orderNo = {}'.format(out_trade_no)) return self.reply("OK", True) if record.is_success: logger.info('recharge record has done. orderNo = {}'.format(out_trade_no)) return self.reply("OK", True) payment_gateway = WechatPaymentGateway.from_gateway_key( record.my_gateway, record.pay_gateway_key, record.pay_app_type) # type: WechatPaymentGateway if not payment_gateway.check(raw): raise InvalidSignatureException( errCode = WeChatErrorCode.MY_ERROR_SIGNATURE, errMsg = u'支付通知签名错误', client = payment_gateway.client ) if 'payOpenId' in record.attachParas and record.attachParas['payOpenId'] and \ pay_open_id != record.attachParas['payOpenId']: raise WechatValidationError(errmsg = 'invalid openid', lvalue = record.attachParas['payOpenId'], rvalue = pay_open_id, client = payment_gateway.client) if 'payOpenId' in record.extraInfo and record.extraInfo['payOpenId'] and \ pay_open_id != record.extraInfo['payOpenId']: raise WechatValidationError(errmsg = 'invalid openid', lvalue = record.extraInfo['payOpenId'], rvalue = pay_open_id, client = payment_gateway.client) self.verify(payment_gateway, record, raw) result_code = raw['result_code'] if result_code != 'SUCCESS': try: errorMsg = u'%s(%s)' % (raw.get('err_code_des', u'微信支付返回错误'), result_code) logger.debug('%s failure confirmed. message = %s' % (repr(record), errorMsg)) record.fail(wxOrderNo = wx_order_no, finishedTime = datetime.datetime.now(), desciprtion = errorMsg) return self.reply('OK', True) except Exception as e: logger.exception('save recharge record failure. code = %s; error = %s; record = %s' % ( result_code, e, repr(record))) return self.reply(u'数据记录失败', False) order_cache_mgr = OrderCacheMgr(record) try: if OrderCacheMgr(record).check_and_set_done(): logger.debug('%s has been done because cache in notify.' % repr(record)) return self.reply('OK', True) except Exception as e: logger.error( 'cache key is not exist or exception. key = %s' % order_cache_mgr.key) modified = self.update_record(record, **raw) if not modified: # 如果没有任何记录被更新则说明已经处理了 logger.debug('%s has been done because db in notify' % repr(record)) return self.reply('OK', True) async_operation(post_pay, record = record) logger.info('[pay notifier]%s successfully confirmed' % repr(record)) return self.reply('OK', True) except WeChatException as e: logger.error(repr(e)) return self.reply('ERROR', False) except ExpatError as e: logger.error('parse xml error. may be XXE. e = %s' % (e,)) return WechatPayNotifier.reply('invalid xml', False) except Exception as e: logger.exception(e) return self.reply('UNKNOWN', False) class WechatPullUp(PayPullUp): """ 微信支付获取支付参数。可能作为商户和资金池商户 """ def do(self): # type: ()->HttpResponse OrderCacheMgr(self.record).initial() try: data = self.payment_gateway.generate_js_payment_params( payOpenId = self.openId, out_trade_no = self.record.orderNo, notify_url = self.payload['notifyUrl'], money = self.record.money, body = self.record.subject, attach = {'dealerId': self.record.ownerId}) except Exception as e: logger.exception(e) self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc()) raise UserServerException(u'拉起支付发生异常') else: logger.debug('wechat payment signature: %s' % str(data)) 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) data.update({ 'outTradeNo': self.record.orderNo, 'adShow': self.payload.get('showAd', False) }) return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': data})