123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import logging
- from django.http import HttpResponse
- from django.utils.module_loading import import_string
- from typing import TYPE_CHECKING, Optional
- from apilib.systypes import Singleton
- from apilib.utils_sys import memcache_lock
- from apps.web.core import PayAppType
- from library.alipay import AliPayNetworkException, AliException
- from library.wechatbase.exceptions import WechatNetworkException, WeChatException
- if TYPE_CHECKING:
- from django.core.handlers.wsgi import WSGIRequest
- from apps.web.common.transaction.pay import RefundRecordT
- from apilib.monetary import RMB
- from apps.web.user.models import RechargeRecord
- from apps.web.dealer.models import DealerRechargeRecord
- from apps.web.common.models import RefundOrderBase
- logger = logging.getLogger(__name__)
- class RefundNotifier(object):
- """
- 退款通知基类
- 退款通知主要负责处理三件事情
- 1. 接收数据、解密数据并校验数据的有效性(时效性以及正确性)
- 2. 对于第三方机构进行回复 (外层函数根据是否抛出异常以及异常状态 执行相应的回复策略)
- 3. 相关联订单的状态 信息进行修改、补充、完成 (do 函数执行)
- """
- def __init__(self, request, refund_order_getter):
- self.payload = self.parse_request(request)
- self.refund_order_getter = refund_order_getter
- def parse_request(self, request): # type:(WSGIRequest) -> dict
- """ 从wsgiRequest 中解析出加密数据 """
- raise NotImplementedError(u"需要实现")
- def verify_payload(self, payload): # type:(dict) -> bool
- """ 验证解密后的数据是否有效 """
- raise NotImplementedError(u"需要实现")
- def handle_refund_order(self, refundOrder, refund_post_callable): # type:(RefundRecordT, callable) -> None
- """ 具体的处理订单的逻辑 根据回调中的成功与否决定下一步的走向"""
- raise NotImplementedError(u"需要实现")
- def do(self, post_refund):
- logger.debug(u"refundNotifier {} do work, payload = {}".format(self.__class__.__name__, self.payload))
- if not self.verify_payload(self.payload):
- logger.info(
- u"refundNotifier {} verify payload error, payload = {}".format(
- self.__class__.__name__, self.payload))
- return HttpResponse(self.successResponse)
- refundOrder = self.refund_order_getter(self.refund_order_filter) # type: RefundRecordT
- if not refundOrder:
- logger.info(
- u"refundNotifier {} record not refund, payload = {}".format(self.__class__.__name__, self.payload))
- return HttpResponse(self.successResponse)
- with memcache_lock(key = str(refundOrder.id), value = 1, expire = 60) as acquired:
- if not acquired:
- logger.warning(u"refundNotifier {}, record = {}, not get lock key".format(
- self.__class__.__name__, refundOrder.id))
- return
- self.handle_refund_order(refundOrder, post_refund)
- return HttpResponse(self.successResponse)
- @property
- def successResponse(self): # type:() -> str
- """ 回复异步回调 告知成功处理 不需要再次重发 """
- raise NotImplementedError(u"需要实现")
- @property
- def errorResponse(self): # type:() -> str
- """ 回复异步回调 告知未成功处理 需要重发 """
- raise NotImplementedError(u"需要实现")
- @property
- def refund_order_filter(self): # type: ()->dict
- raise NotImplementedError(u"需要实现")
- class RefundPuller(object):
- """ 拉取退款单的详情 """
- def __init__(self, refundOrder): # type:(RefundRecordT) -> None
- self._refundOrder = refundOrder
- def pull(self, refund_post_callable, **kwargs): # type:(callable, dict) -> bool
- raise NotImplementedError(u"must implement pull.")
- def parse_error(self, errorCode, errorDesc, refund_post_callable):
- raise NotImplementedError(u"must implement parse_error.")
- class RefundCashMixin(object):
- def __init__(self, rechargeOrder, refundFee):
- # type:(Optional[RechargeRecord, DealerRechargeRecord], RMB) -> None
- self.paySubOrder = rechargeOrder
- self.payOrder = self.paySubOrder.payOrder
- self.refundFee = refundFee
- # self._nextSeq = 1
- @property
- def outTradeNo(self):
- """
- 交易单号
- :return:
- """
- return self.payOrder.orderNo
- @property
- def totalFee(self):
- return self.payOrder.money
- @property
- def totalCoins(self):
- return self.payOrder.coins
- @property
- def subTotalFee(self):
- return self.paySubOrder.money
- @property
- def subTotalCoins(self):
- return self.paySubOrder.coins
- @property
- def refund_paras(self):
- return 'mixOrderNo = {} totalFee = {} orderNo = {} subTotalFee = {} refundFee = {}'.format(
- self.payOrder.orderNo, self.totalFee, self.paySubOrder.orderNo, self.subTotalFee, self.refundFee)
- def submit_refund(self, refundOrder, partition_map, reason, notify_url, post_refund):
- # type:(RefundOrderBase, Optional[dict], basestring, basestring, callable)->None
- payGateway = refundOrder.my_payment_gateway
- if payGateway.pay_app_type == PayAppType.ALIPAY:
- # 支付宝的退款方式
- # 支付宝的退款很特殊,接口状态以及业务状态均在同步接口中返回 其中 code = 10000 表示接口成功 即申请成功了 fund_change= Y 表示退款成功
- # 而当接口状态成功 code=10000 但是资金未发生变动 fund_change=N 的时候,则退款是不成功的(最好需要轮询一次),此时不改变退款单的状态
- try:
- result = payGateway.refund_to_user(
- out_trade_no = self.outTradeNo,
- out_refund_no = refundOrder.orderNo,
- refund_fee = refundOrder.money,
- total_fee = self.totalFee,
- refund_reason = reason)
- logger.debug('AliPay Refund request successfully! return = {}'.format(result))
- if result['code'] == '10000': # 接口调用成功
- if result['fund_change'] == 'Y':
- if "gmt_refund_pay" in result:
- finishedTime = datetime.datetime.strptime(result["gmt_refund_pay"], "%Y-%m-%d %H:%M:%S")
- else:
- finishedTime = datetime.datetime.now()
- matched = refundOrder.succeed(tradeRefundNo = result['trade_no'], finishedTime = finishedTime)
- if matched:
- post_refund(refundOrder, True)
- elif result['code'] == '20000': # 20000 服务不可用 稍后重试
- refundOrder.fail(errorCode = result.get("code"), errorDesc = result.get("msg"))
- else: # 其他都代表失败
- if result['code'] == '40004': # 业务错误
- if result['sub_code'] in [
- 'ACQ.REFUND_AMT_NOT_EQUAL_TOTAL',
- 'ACQ.REASON_TRADE_REFUND_FEE_ERR',
- 'ACQ.TRADE_NOT_ALLOW_REFUND',
- 'ACQ.REFUND_FEE_ERROR',
- 'ACQ.BUYER_NOT_EXIST',
- 'ACQ.ONLINE_TRADE_VOUCHER_NOT_ALLOW_REFUND',
- 'ACQ.TRADE_HAS_FINISHED'
- ]:
- matched = refundOrder.closed(
- errorCode = result.get("sub_code"), errorDesc = result.get("sub_msg"))
- if matched:
- post_refund(refundOrder, False)
- elif result['sub_code'] in ['ACQ.SELLER_BALANCE_NOT_ENOUGH']:
- # 40004-ACQ.SELLER_BALANCE_NOT_ENOUGH: 卖家余额不足
- refundOrder.fail(errorCode = result.get("sub_code"), errorDesc = result.get("sub_msg"))
- else:
- refundOrder.fail(errorCode = result.get("sub_code"), errorDesc = result.get("sub_msg"))
- else:
- refundOrder.fail(errorCode = result.get("sub_code"), errorDesc = result.get("sub_msg"))
- except AliPayNetworkException as e:
- # 网络调用失败, 无法判定是否成功, 不改变状态, 等拉取订单状态后在更改订单状态
- logger.warning(
- 'AliPay Refund request failure! orderNo = {}; e = {}'.format(refundOrder.orderNo, str(e)))
- except AliException as e:
- # 目前只有签名失败
- logger.warning(
- 'AliPay Refund request failure! orderNo = {}; e = {}'.format(refundOrder.orderNo, str(e)))
- refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
- elif payGateway.pay_app_type in [PayAppType.WECHAT]:
- try:
- result = payGateway.refund_to_user(
- out_trade_no = self.outTradeNo,
- out_refund_no = refundOrder.orderNo,
- refund_fee = refundOrder.money,
- total_fee = self.totalFee,
- refund_reason = reason,
- notify_url = notify_url)
- logger.debug('WeChat Refund request successfully! return = {}'.format(result))
- except WechatNetworkException as e:
- logger.warning(
- 'WeChat Refund request exception! orderNo = {}; e = {}'.format(refundOrder.orderNo, str(e)))
- except WeChatException as e:
- logger.warning(
- 'WeChat Refund request failure! orderNo = {}; e = {}'.format(refundOrder.orderNo, str(e)))
- puller = RefundManager().get_poller(refundOrder.pay_app_type)
- puller(refundOrder).parse_error(e.errCode, e.errMsg, post_refund)
- else:
- matched = refundOrder.closed(errorCode = u'NOT_SUPPORT_REFUND', errorDesc = u"不支持的退款模式")
- if matched:
- post_refund(refundOrder, False)
- def check_wallet(self, proxy, order):
- pass
- def pre_check(self):
- """
- 退款的预检查
- :return:
- """
- raise NotImplementedError('must implement pre_check')
- class RefundManager(Singleton):
- def __init__(self):
- super(RefundManager, self).__init__()
- self.map_dict = {
- PayAppType.WECHAT:
- {
- 'poller': import_string('apps.web.common.transaction.refund.wechat.WechatRefundPuller'),
- 'notifier': import_string('apps.web.common.transaction.refund.wechat.WechatRefundNotifier'),
- },
- PayAppType.ALIPAY: {
- 'poller': import_string('apps.web.common.transaction.refund.alipay.AliRefundPuller'),
- 'notifier': import_string('apps.web.common.transaction.refund.alipay.AliRefundNotifier'),
- },
- }
- 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']
|