# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging from django.conf import settings from typing import TYPE_CHECKING from apilib.monetary import RMB, Percent from apilib.utils_sys import memcache_lock from apps.web.common.transaction.refund import RefundCashMixin from apps.web.dealer.define import DealerConst from apps.web.dealer.models import RefundDealerRechargeRecord, DealerRechargeRecord, Dealer from apps.web.dealer.proxy import record_income_proxy from apps.web.exceptions import UserServerException from apps.web.user.models import RechargeRecord logger = logging.getLogger(__name__) if TYPE_CHECKING: pass def refund_post_pay(refundOrder, success): pass def refund_cash_to_dealer(dealer_recharge_record, refundFee, isInsure = False): if dealer_recharge_record.product == DealerRechargeRecord.ProductType.SimCard: return RefundSimRecharge(dealer_recharge_record, refundFee, u'流量卡退款').execute( frozen_callable = None, refund_callable = refund_post_pay) elif dealer_recharge_record.product == DealerRechargeRecord.ProductType.AutoSimCard: # return RefundSimWallet(dealer_recharge_record, refundFee).execute() # else: raise UserServerException(u'目前不支持该种类型订单退款') else: raise UserServerException(u'目前不支持该种类型订单退款') class RefundCash(RefundCashMixin): MAX_LEDGER_CHECK_TIME = 15 # 最长的查询分账时间 def __init__(self, rechargeOrder, refundFee, reason): # type:(DealerRechargeRecord, RMB, basestring) -> None super(RefundCash, self).__init__(rechargeOrder, refundFee) self.reason = reason def pre_check(self): """ 退款的预检查 :return: """ # 首先检查退款的金额 原则上退款金额不能小于0 不能大于交易定安的金额 if self.refundFee <= RMB(0) or self.refundFee > self.totalFee: raise UserServerException(u"退费金额错误") refundOrder = RefundDealerRechargeRecord.objects.filter( rechargeObjId = self.paySubOrder.id).first() # type: RefundDealerRechargeRecord if refundOrder: if refundOrder.is_successful: raise UserServerException(u"该单已经退单") else: raise UserServerException(u"订单正在退款中") def execute(self, frozen_callable, refund_callable, notify_url = None): """ 执行退款的动作 :return: """ lockKey = "refund_dealer_recharge_cash_{}".format(self.paySubOrder.id) with memcache_lock(key = lockKey, value = self.paySubOrder.id, expire = 360) as acquired: if not acquired: raise UserServerException(u"退款订单正在处理,等订单结束后,您才能再次重试哦") self.pre_check() refundOrder = RefundDealerRechargeRecord.issue(self.payOrder, self.refundFee) logger.info('refund paras: {} {}'.format(refundOrder.orderNo, self.refund_paras)) refundOrder.processing() try: self.submit_refund( refundOrder, None, self.reason, notify_url or refundOrder.notify_url, refund_callable) except Exception: import traceback logger.warning( 'Refund request failure! orderNo = {}; e = {}'.format(refundOrder.orderNo, traceback.format_exc())) finally: pass return refundOrder class RefundSimRecharge(RefundCash): def pre_check(self): super(RefundSimRecharge, self).pre_check() for partition in self.payOrder.settleInfo['partition']: if partition['id'] != settings.MY_PRIMARY_AGENT_ID and partition['earned'] > 0: raise UserServerException(u'目前仅支持不分账情况下退账。') class RefundInsureRecharge(RefundCash): def pre_check(self): if self.refundFee <= RMB(0) or self.refundFee > self.totalFee: # DealerChargeRecord中的退费总额是负数 raise UserServerException(u"退费金额错误") # 目前只支持一个单,退一次,不允许退多次 refundOrder = RefundDealerRechargeRecord.objects.filter( rechargeObjId = self.paySubOrder.id).first() # type: RefundDealerRechargeRecord if refundOrder: if refundOrder.is_successful: raise UserServerException(u"该单已经退单") else: raise UserServerException(u"订单正在退款中") class RefundToWallet(object): def __init__(self, rechargeOrder, refundFee, reason, subType, via, source): self._payOrder = rechargeOrder self.refundFee = refundFee self.reason = reason self.subType = subType self.via = via self.source = source @property def outTradeNo(self): """ 交易单号 :return: """ return self._payOrder.orderNo @property def totalFee(self): return RMB(round(float(self._payOrder.totalFee) / 100, 2)) @property def payOrder(self): """ 交易订单 即支付订单 与第三方系统产生关联的订单 :return: """ return self._payOrder def pre_check(self): """ 退款的预检查 :return: """ # 首先检查退款的金额 原则上退款金额不能小于0 不能大于交易定安的金额 if self.refundFee <= RMB(0) or self.refundFee > self.totalFee: raise UserServerException(u"退费金额错误") # 检查退款订单是否已经存在 是否已经退款成功 refundOrder = RefundDealerRechargeRecord.objects.filter( rechargeObjId = self._payOrder.id).first() # type: RefundDealerRechargeRecord if refundOrder: if refundOrder.is_successful: raise UserServerException(u"该单已经退单") else: raise UserServerException(u"订单正在退款中") def refund_dealer_balance(self, refundFee): source_key = self._payOrder.withdraw_source_key income_type = DealerConst.MAP_SOURCE_TO_TYPE[self.source] dealer = Dealer.objects(id = self._payOrder.dealerId).first() if not dealer: return False fundKey = dealer.fund_key(income_type = income_type, source_key = source_key) queryFilter = { '_id': dealer.id } update = { '$inc': {'{fundKey}.balance'.format(fundKey = fundKey): (refundFee).mongo_amount} } result = Dealer.get_collection().update_one(queryFilter, update, upsert = False) if result.matched_count == 1 and result.modified_count == 1: return True else: return False def execute(self, refund_callable = None): """ 执行退款的动作 :return: """ lockKey = "refund_dealer_recharge_wallet_{}".format(self._payOrder.id) with memcache_lock(key = lockKey, value = self._payOrder.id, expire = 360) as acquired: if not acquired: raise UserServerException(u"退款订单正在处理,等订单结束后,您才能再次重试哦") self.pre_check() refundOrder = RefundDealerRechargeRecord.issue(self.payOrder, self.refundFee) logger.info( 'refund[wallet], order = %s out_refund_no=%s, out_trade_no=%s, refund_fee=%s, total_fee=%s' % ( self._payOrder.orderNo, self.payOrder.orderNo, refundOrder.orderNo, self.refundFee, str(float(self.payOrder.totalFee) / 100))) refundOrder.processing() try: self.refund_dealer_balance(self.refundFee) # 先把钱包的余额返回 income_record = RechargeRecord.issue_refund_order(self.payOrder, self.refundFee, self.subType, self.via) # 补充一条充值记录 except Exception as e: logger.exception(e) refundOrder.fail(errorCode = 'EXCEPTION', errorDesc = u'退款过程发生异常') raise e record_income_proxy(self.source, income_record, { "owner": [ { "money": RMB(income_record.money).mongo_amount, "role": "owner", "share": Percent("100.0").mongo_amount, "id": str(income_record.ownerId) } ], 'partner': [] }) refundOrder.succeed(finishedTime = datetime.datetime.now()) if refund_callable: # 用于退款到钱包后的善后工作,比如扣除代理商的分成,补充退款记录 refund_callable(refundOrder, True) return refundOrder