# -*- 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.monetary import RMB from apilib.utils_json import JsonResponse from apps.web.common.transaction.pay import PayNotifier, OrderCacheMgr, PayRecordPoller, PayNotifyAction, PayPullUp from apps.web.constant import AppPlatformType, PollRecordDefine from apps.web.core import PayAppType from apps.web.core.payment import PaymentGateway from apps.web.core.utils import async_operation, JsonOkResponse from apps.web.exceptions import UserServerException from library.jdopen.exceptions import JdOpenException, JDOpenValidationError from taskmanager.mediator import task_caller if TYPE_CHECKING: from apps.web.common.transaction.pay import RechargeRecordT from apps.web.core.payment.jdopen import JDOpenPaymentGateway logger = logging.getLogger(__name__) def update_record(record, **payload): # type: (RechargeRecordT, Dict)->bool import arrow finished_time = arrow.get(payload['completeTime'], 'YYYY-MM-DD HH:mm:ss', tzinfo=settings.TIME_ZONE).naive jdOrderNo = payload['orderNum'] # 京东系统订单号. 京东系统查单 if 'bankOutTradeNum' in payload and payload['bankOutTradeNum']: transactionId = payload['bankOutTradeNum'] elif 'bankRequestNum' in payload and payload['bankRequestNum']: transactionId = payload['bankRequestNum'] else: transactionId = jdOrderNo _payload = { 'extraInfo.bankRequestNum': payload.get('bankRequestNum', ''), 'extraInfo.bankOutTradeNum': payload.get('bankOutTradeNum', ''), 'extraInfo.payload': payload, 'extraInfo.notify_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') } if 'ledgerStatus' in payload and payload['ledgerStatus']: _payload.update({ 'extraInfo.ledgerStatus': payload['ledgerStatus'] }) return record.succeed(wxOrderNo=jdOrderNo, transactionId=transactionId, finishedTime=finished_time, **_payload) class JDOpenPayReordPoller(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: (JDOpenPaymentGateway, RechargeRecordT)->(PayNotifyAction, str) try: result = payment_gateway.api_trade_query(out_trade_no=record.orderNo) if 'status' not in result: return PayNotifyAction.Unknown, result if result['status'] == 'SUCCESS': return PayNotifyAction.NeedHandle, { 'completeTime': result['completeTime'], 'orderNum': result['orderNum'], 'bankRequestNum': result['payRecordList'][0]['bankRequestNum'], 'bankOutTradeNum': result['payRecordList'][0]['bankOutTradeNum'] } if result['payStatus'] in ['FAIL', 'REFUND', 'CANCEL', 'CLOSE']: return PayNotifyAction.NoHandle, result return PayNotifyAction.Unknown, result except JdOpenException as e: logger.error(str(e)) return PayNotifyAction.Unknown, {} except Exception, e: logger.exception(e) return PayNotifyAction.Unknown, {} class JDOpenPayNotifier(PayNotifier): def __init__(self, request, record_cls_factory): super(JDOpenPayNotifier, self).__init__(request=request, record_cls_factory=record_cls_factory) self.body = request.body def parse_request(self, request): _head = { 'accessKey': request.META['HTTP_ACCESSKEY'], 'version': request.META['HTTP_VERSION'], 'timestamp': request.META['HTTP_TIMESTAMP'], 'token': request.META['HTTP_TOKEN'] } _body = json.loads(request.body) # type: Dict logger.debug('received jdopen pay notify: body = {}; heads = {}'.format(_body, _head)) return { 'body': _body, 'head': _head } @property def out_trade_no(self): return self.payload['body']['requestNum'] def verify(self, payment_gateway, record, payload): # type: (JDOpenPaymentGateway, RechargeRecordT, Dict)->None notifier_yuan = RMB(payload['body']['orderAmount']) if record.money != notifier_yuan: raise JDOpenValidationError( tips=u'无效的total_fee', lvalue=str(record.money), rvalue=str(notifier_yuan), client=payment_gateway.client) def update_record(self, record, **payload): # type: (RechargeRecordT, Dict)->bool return update_record(record, **payload) def reply(self): return HttpResponse('ok') def check_and_update_ledger_status(self, record): if 'ledgerStatus' in self.payload['body'] and self.payload['body']['ledgerStatus']: old_ledger_status = record.extraInfo.get('ledgerStatus', '') if old_ledger_status != self.payload['body']['ledgerStatus']: record.update(extraInfo__ledgerStatus = self.payload['body']['ledgerStatus']) def do(self, post_pay): # type: (callable)->HttpResponse try: order_no = self.out_trade_no record = self.record_cls.get_record(order_no=order_no) # type: Optional[RechargeRecordT] if not record: logger.error('no such record'.format(order_no)) return self.reply() if self.payload['body']['status'] != 'SUCCESS': logger.error('status of record is not success.'.format(order_no)) return self.reply() if record.is_success(): logger.error( 'record has been finished.'.format(str(record.id), order_no)) self.check_and_update_ledger_status(record) return self.reply() pay_gateway = PaymentGateway.from_gateway_key( record.my_gateway, record.pay_gateway_key, record.pay_app_type) # type: JDOpenPaymentGateway pay_gateway.check_token(self.body, self.payload['head']['timestamp'], self.payload['head']['token']) self.verify(pay_gateway, record, self.payload) 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))) self.check_and_update_ledger_status(record) return self.reply() 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, **(self.payload['body'])) if not modified: # 如果没有任何记录被更新则说明已经处理了 logger.debug('{} has been done because db in notify'.format(repr(record))) self.check_and_update_ledger_status(record) return self.reply() logger.info('{} successfully confirmed'.format(repr(record))) async_operation(post_pay, record=record) return self.reply() except JdOpenException as e: logger.exception(e) return self.reply() except Exception as e: logger.exception(e) class JDOpenPullUp(PayPullUp): def do(self): # type: ()->HttpResponse gateway_type = self.payment_gateway.gateway_type if gateway_type not in [AppPlatformType.ALIPAY, AppPlatformType.WECHAT, AppPlatformType.JD]: raise UserServerException(u'不支持该支付类型') OrderCacheMgr(self.record).initial() try: ledger_type, ledger_fee_assume, ledger_list = self.payment_gateway.bill_split_rule( self.record.partition_map) _payload = { 'out_trade_no': self.record.orderNo, 'money': self.record.money, 'notify_url': self.payload['notifyUrl'], 'subject': self.record.subject, 'attach': {'dealerId': self.record.ownerId}, 'openId': self.openId } if ledger_list: _payload.update({ 'ledgerRule': { 'ledgerType': ledger_type, 'ledgerFeeAssume': ledger_fee_assume, 'list': ledger_list }}) pay_info = self.payment_gateway.unified_order(**_payload) except JdOpenException as e: logger.exception(e) self.record.fail(description = e.errMsg) raise UserServerException(e.errMsg) except Exception as e: logger.exception(e) self.record.fail(description = u'拉起支付发生异常', extraInfo__traceback = traceback.format_exc()) raise UserServerException(u'拉起支付发生异常') else: logger.debug('jdopen unified order pay info = {}'.format(str(pay_info))) self.record.update( wxOrderNo = pay_info['data']['bankRequestNum'], extraInfo__jdOrderNo = pay_info['data']['orderNum']) gateway_type = self.payment_gateway.gateway_type if gateway_type == AppPlatformType.ALIPAY: response = JsonOkResponse(payload = { 'tradeNO': pay_info['data']['bankRequest']['TRADENO'], 'outTradeNo': self.record.orderNo, 'adShow': self.payload.get('showAd') }) elif gateway_type == AppPlatformType.WECHAT: response = JsonOkResponse(payload = { 'outTradeNo': self.record.orderNo, 'golden': True, 'adShow': self.payload.get('showAd'), 'appId': pay_info['data']['bankRequest']['APPID'], 'timeStamp': pay_info['data']['bankRequest']['TIMESTAMP'], 'nonceStr': pay_info['data']['bankRequest']['NONCESTR'], 'signType': pay_info['data']['bankRequest']['SIBGTYPE'], # 小程序和公众号返回的值不是一个 'package': pay_info['data']['bankRequest']['PACKAGE'], 'paySign': pay_info['data']['bankRequest']['PAYSIGN'] }) else: # gateway_type == AppPlatformType.JD response = JsonResponse({ 'result': 1, 'description': 'SUCCESS', 'payload': { 'payUrl': pay_info['data']['bankRequest']['PAY_URL'] }}) 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 response