# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import xmltodict from typing import TYPE_CHECKING from apps.web.common.transaction.refund import RefundNotifier, RefundPuller from apps.web.core.models import WechatPayApp from library.wechatbase.exceptions import WeChatException, WechatNetworkException from library.wechatpy.pay import WeChatPay logger = logging.getLogger(__name__) if TYPE_CHECKING: from django.core.handlers.wsgi import WSGIRequest from apps.web.core.payment.type_checking import PaymentGatewayT from apps.web.common.transaction.pay import RefundRecordT class WechatRefundNotifier(RefundNotifier): def parse_request(self, request): # type:(WSGIRequest) -> dict payload = xmltodict.parse(request.body)['xml'] # type: dict # xml 解析之后获取第一步的数据 找到wechat app app = WechatPayApp.objects.get(appid = payload["appid"], mchid = payload['mch_id']) # type: WechatPayApp req_info = WeChatPay(appid = app.appid, api_key = app.apikey, mch_id = app.mchid).decrypt(payload["req_info"]) # 所有信息打包成一个字典返回 payload.update(req_info) return payload def verify_payload(self, payload): # type:(dict) -> bool """ 忽略对签名串的校验 """ return True def handle_refund_order(self, refundOrder, refund_post_callable): # type:(RefundRecordT, callable) -> None if not self.payload or self.payload["return_code"] != "SUCCESS": return if self.payload["refund_status"] == "SUCCESS": payFinishTime = self.payload["success_time"] datetimeRefund = datetime.datetime.strptime(payFinishTime, "%Y-%m-%d %H:%M:%S") matched = refundOrder.succeed(finishedTime = datetimeRefund, **{ 'tradeRefundNo': self.payload["refund_id"] }) if not matched: return return refund_post_callable(refundOrder, True) elif self.payload["refund_status"] == "REFUNDCLOSE": refundOrder.fail( errorCode = 'REFUNDCLOSE', errorDesc = self.payload["return_msg"], **{ 'tradeRefundNo': self.payload.get("refund_id") }) return elif self.payload["refund_status"] == "CHANGE": refundOrder.fail( errorCode = 'CHANGE', errorDesc = u'退款异常', **{ 'tradeRefundNo': self.payload.get("refund_id") }) else: pass @property def refund_order_filter(self): # type:() -> dict return {'orderNo': self.payload["out_refund_no"]} @property def errorResponse(self): # type:() -> str from apps.web.core.payment.wechat import WechatPaymentGateway return WechatPaymentGateway.reply("", False) @property def successResponse(self): # type:() -> str from apps.web.core.payment.wechat import WechatPaymentGateway return WechatPaymentGateway.reply("", True) class WechatRefundPuller(RefundPuller): def parse_error(self, errorCode, errorDesc, refund_post_callable): if errorCode == 'REFUNDNOTEXIST': self._refundOrder.no_order(errorCode = errorCode, errorDesc = errorDesc) elif errorCode in ['TRADE_OVERDUE', 'USER_ACCOUNT_ABNORMAL']: # USER_ACCOUNT_ABNORMAL: 用户账户异常或已注销,不能原路退回,请使用其他方式进行退款。 # TRADE_OVERDUE: 超期订单无法退款 matched = self._refundOrder.closed(errorCode = errorCode, errorDesc = errorDesc) if matched: refund_post_callable(self._refundOrder, False) return True else: if errorCode in ['NOTENOUGH']: # NOTENOUGH: 基本账户余额不足,请充值后重新发起 self._refundOrder.fail(errorCode = errorCode, errorDesc = errorDesc) else: # 其他暂时不处理,逐步补充 logger.warning('RefundOrder, errorCode = {}, errorDesc = {}'.format( self._refundOrder.orderNo, errorCode, errorDesc)) return True return False def pull(self, refund_post_callable, **kwargs): # type:(callable, dict) -> bool payGateway = self._refundOrder.my_payment_gateway # type: PaymentGatewayT try: result = payGateway.api_refund_query(out_refund_no = self._refundOrder.orderNo) except WechatNetworkException as e: # return_code不为SUCCESS的情况下 raise e except WeChatException as e: # result_code不为SUCCESS的情况下, 抛出异常 return self.parse_error(e.errCode, e.errMsg, refund_post_callable) else: # 找出退款单号 offset = None for _k, _v in result.items(): if _v == self._refundOrder.orderNo: offset = _k.rsplit("_", 1)[1] break if not offset: return False refundStatus = result["refund_status_{}".format(offset)] if refundStatus == "SUCCESS": refundTime = result["refund_success_time_{}".format(offset)] datetimeRefund = datetime.datetime.strptime(refundTime, "%Y-%m-%d %H:%M:%S") matched = self._refundOrder.succeed( finishedTime = datetimeRefund, tradeRefundNo = result["refund_id_{}".format(offset)]) if matched: refund_post_callable(self._refundOrder, True) return True elif refundStatus == "REFUNDCLOSE": self._refundOrder.fail( errorCode = "REFUNDCLOSE", errorDesc = '{}({})'.format(result.get("err_code_des"), result.get("err_code"))) elif refundStatus == "PROCESSING": if not (self._refundOrder.is_processing or self._refundOrder.is_closed or self._refundOrder.is_success): self._refundOrder.processing() return True elif refundStatus == "CHANGE": matched = self._refundOrder.closed( errorCode = 'CHANGE', errorDesc = '{}({})'.format(result.get("err_code_des"), result.get("err_code")), **{ 'tradeRefundNo': result.get("refund_id_{}".format(offset)) }) if matched: refund_post_callable(self._refundOrder, False) return True else: pass return False