|
@@ -6,28 +6,27 @@ import logging
|
|
|
import time
|
|
|
import uuid
|
|
|
|
|
|
-from pymongo.errors import DuplicateKeyError
|
|
|
-from typing import TYPE_CHECKING, Dict, Any
|
|
|
+from bson import ObjectId
|
|
|
+from mongoengine import NotUniqueError
|
|
|
+from typing import TYPE_CHECKING, Dict
|
|
|
|
|
|
from apilib.monetary import VirtualCoin, RMB
|
|
|
-from apps.web.common.transaction.pay import RefundManager
|
|
|
-from apps.web.constant import USER_RECHARGE_TYPE, RechargeRecordVia
|
|
|
+from apilib.utils import flatten
|
|
|
+from apps.web.common.proxy import ClientDealerIncomeModelProxy
|
|
|
+from apps.web.common.transaction.refund import RefundCashMixin, RefundManager
|
|
|
+from apps.web.constant import USER_RECHARGE_TYPE, PARTITION_ROLE, RechargeRecordVia, AppPlatformType
|
|
|
from apps.web.core import PayAppType, ROLE
|
|
|
from apps.web.core.exceptions import ParameterError
|
|
|
-from apps.web.core.payment import PaymentGateway
|
|
|
-from apps.web.dealer.define import DEALER_INCOME_SOURCE
|
|
|
-from apps.web.dealer.proxy import DealerIncomeProxy
|
|
|
+from apps.web.core.payment import WithdrawGateway
|
|
|
+from apps.web.dealer.define import DEALER_INCOME_SOURCE, DEALER_INCOME_TYPE
|
|
|
from apps.web.device.models import Group
|
|
|
from apps.web.exceptions import UserServerException
|
|
|
-from apps.web.user.conf import REFUND_NOTIFY_URL
|
|
|
-from apps.web.user.models import MyUser, RechargeRecord, RefundMoneyRecord
|
|
|
-from library.alipay import AliException
|
|
|
-from library.wechatbase.exceptions import WeChatPayException
|
|
|
+from apps.web.user.models import MyUser, RechargeRecord, RefundMoneyRecord, Card, UserVirtualCard, MonthlyPackage
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
- pass
|
|
|
+ from apps.web.dealer.proxy import DealerIncomeProxy
|
|
|
|
|
|
|
|
|
def refund_money(device, money, openId):
|
|
@@ -63,61 +62,74 @@ def refund_money(device, money, openId):
|
|
|
logger.exception('update record for feedback coins error=%s,orderNo=%s' % (e, orderNo))
|
|
|
|
|
|
|
|
|
-def refund_cash(recharge_record, refundFee, deductCoins, **kwargs):
|
|
|
- # type:(RechargeRecord, RMB, VirtualCoin, Dict[str, Any])->RefundMoneyRecord
|
|
|
+def refund_cash(recharge_record, refundFee, **kwargs):
|
|
|
+ # type:(RechargeRecord, RMB, Dict)->RefundMoneyRecord
|
|
|
|
|
|
"""
|
|
|
新的执行退款 为了保持导包顺序不变
|
|
|
- :param deductCoins:
|
|
|
+
|
|
|
:param recharge_record:
|
|
|
:param refundFee:
|
|
|
- :param kwargs 用户为资金实体的情况下, 传入user和minus_total_consume参数
|
|
|
+ :type kwargs: object
|
|
|
:return:
|
|
|
"""
|
|
|
|
|
|
- if recharge_record.via in [RechargeRecordVia.Balance, RechargeRecordVia.Cash, RechargeRecordVia.StartDevice]:
|
|
|
- return RefundCash(recharge_record, refundFee, deductCoins).execute(
|
|
|
- frozen_callable = frozen_refund_for_balance, **kwargs)
|
|
|
+ if recharge_record.via in [
|
|
|
+ RechargeRecordVia.Balance,
|
|
|
+ RechargeRecordVia.Cash,
|
|
|
+ RechargeRecordVia.Card,
|
|
|
+ RechargeRecordVia.VirtualCard,
|
|
|
+ RechargeRecordVia.MonthlyPackage,
|
|
|
+ ]:
|
|
|
+ return RefundCash(recharge_record, refundFee, **kwargs).execute(
|
|
|
+ frozen_callable = frozen_refund_func, refund_callable = refund_post_pay)
|
|
|
else:
|
|
|
raise UserServerException(u'不支持该类型订单退款')
|
|
|
|
|
|
|
|
|
-class RefundCash(object):
|
|
|
- # 最长的查询分账时间
|
|
|
- MAX_LEDGER_CHECK_TIME = 15
|
|
|
+class RefundCash(RefundCashMixin):
|
|
|
+ MAX_LEDGER_CHECK_TIME = 15 # 最长的查询分账时间
|
|
|
|
|
|
- def __init__(self, rechargeOrder, refundFee, deductCoins): # type:(RechargeRecord, RMB, VirtualCoin) -> None
|
|
|
- self.paySubOrder = rechargeOrder
|
|
|
- self.payOrder = self.paySubOrder.payOrder
|
|
|
+ def __init__(self, rechargeOrder, refundFee, **kwargs):
|
|
|
+ # type:(RechargeRecord, RMB, dict) -> None
|
|
|
|
|
|
- self.refundFee = refundFee
|
|
|
- self.deductCoins = deductCoins
|
|
|
+ super(RefundCash, self).__init__(rechargeOrder, refundFee)
|
|
|
|
|
|
- # self._nextSeq = 1
|
|
|
+ self.extraInfo = kwargs
|
|
|
|
|
|
- @property
|
|
|
- def outTradeNo(self):
|
|
|
- """
|
|
|
- 交易单号
|
|
|
- :return:
|
|
|
- """
|
|
|
- return self.payOrder.orderNo
|
|
|
+ # self._nextSeq = 1
|
|
|
|
|
|
- @property
|
|
|
- def totalFee(self):
|
|
|
- return self.payOrder.money
|
|
|
+ def check_wallet(self, proxy, order):
|
|
|
+ partition_map = proxy.partition_map
|
|
|
+ for partition in list(flatten(partition_map.values())):
|
|
|
+ if partition['role'] == PARTITION_ROLE.OWNER:
|
|
|
+ leftBalance = order.owner.sub_balance(
|
|
|
+ income_type = DEALER_INCOME_TYPE.DEVICE_INCOME,
|
|
|
+ source_key = order.withdraw_source_key,
|
|
|
+ only_ledger = True)
|
|
|
+ if abs(RMB(partition['money'])) > leftBalance:
|
|
|
+ raise UserServerException(u"您的钱包余额不足,无法退款。")
|
|
|
+
|
|
|
+ elif partition['role'] == PARTITION_ROLE.PARTNER:
|
|
|
+ from apps.web.dealer.models import Dealer
|
|
|
+ dealer = Dealer.objects(id = partition['id']).first()
|
|
|
+ leftBalance = dealer.sub_balance(
|
|
|
+ income_type = DEALER_INCOME_TYPE.DEVICE_INCOME,
|
|
|
+ source_key = order.withdraw_source_key)
|
|
|
+ if abs(RMB(partition['money'])) > leftBalance:
|
|
|
+ raise UserServerException(u"您的分账合伙人钱包余额不足,无法退款(1001)。")
|
|
|
+ else:
|
|
|
+ from apps.web.agent.models import Agent
|
|
|
+ from apps.web.agent.define import AGENT_INCOME_TYPE
|
|
|
|
|
|
- @property
|
|
|
- def totalCoins(self):
|
|
|
- return self.payOrder.coins
|
|
|
+ agent = Agent.objects(id = partition['id']).first()
|
|
|
|
|
|
- @property
|
|
|
- def subTotalFee(self):
|
|
|
- return self.paySubOrder.money
|
|
|
+ leftBalance = agent.sub_balance(
|
|
|
+ income_type = AGENT_INCOME_TYPE.DEALER_DEVICE_FEE,
|
|
|
+ source_key = order.withdraw_source_key)
|
|
|
|
|
|
- @property
|
|
|
- def subTotalCoins(self):
|
|
|
- return self.paySubOrder.coins
|
|
|
+ if abs(RMB(partition['money'])) > leftBalance:
|
|
|
+ raise UserServerException(u"您的分账合伙人钱包余额不足,无法退款(1002)。")
|
|
|
|
|
|
def pre_check(self):
|
|
|
"""
|
|
@@ -125,38 +137,51 @@ class RefundCash(object):
|
|
|
:return:
|
|
|
"""
|
|
|
|
|
|
- if self.refundFee <= RMB(0) or self.refundFee > self.totalFee or self.refundFee > self.subTotalFee:
|
|
|
+ if self.refundFee <= RMB(0) or self.refundFee > self.subTotalFee:
|
|
|
raise ParameterError(u"退费金额错误")
|
|
|
|
|
|
- if self.deductCoins < VirtualCoin(
|
|
|
- 0) or self.deductCoins > self.totalCoins or self.deductCoins > self.subTotalCoins:
|
|
|
- raise ParameterError(u"扣除用户金币数目错误")
|
|
|
+ if 'deductCoins' in self.extraInfo:
|
|
|
+ deductCoins = self.extraInfo['deductCoins']
|
|
|
+ if deductCoins < VirtualCoin(
|
|
|
+ 0) or deductCoins > self.subTotalCoins:
|
|
|
+ raise ParameterError(u"扣除用户金币数目错误")
|
|
|
|
|
|
check_end_time = int(time.time()) + self.MAX_LEDGER_CHECK_TIME
|
|
|
|
|
|
- if not self.paySubOrder.is_success():
|
|
|
+ if not self.paySubOrder.is_success:
|
|
|
raise UserServerException(u'非成功订单无法进行退款')
|
|
|
|
|
|
while not self.paySubOrder.is_ledgered and int(time.time()) < check_end_time:
|
|
|
logger.debug('{} is not allocated. wait to be allocated.'.format(repr(self.paySubOrder)))
|
|
|
|
|
|
- # TODO 考虑回调的方式进行
|
|
|
time.sleep(5)
|
|
|
self.paySubOrder.reload()
|
|
|
|
|
|
- proxy = DealerIncomeProxy.objects.filter(
|
|
|
- ref_id = self.paySubOrder.id).first() # type: DealerIncomeProxy
|
|
|
+ proxy = ClientDealerIncomeModelProxy.get_one(ref_id = self.paySubOrder.id) # type: DealerIncomeProxy
|
|
|
if not proxy:
|
|
|
- raise UserServerException(u"订单尚未分账,无法退款")
|
|
|
+ raise UserServerException(u"订单尚未分账,无法退款(10002)")
|
|
|
+
|
|
|
+ if self.paySubOrder.gateway == AppPlatformType.ALIPAY:
|
|
|
+ over_time = 90 * 24 * 60 * 60
|
|
|
+ else:
|
|
|
+ over_time = 365 * 24 * 60 * 60
|
|
|
+
|
|
|
+ if (datetime.datetime.now() - self.paySubOrder.finishedTime).total_seconds() >= over_time:
|
|
|
+ raise UserServerException(u'超期订单不允许退款')
|
|
|
+
|
|
|
+ checkWallet = self.extraInfo.get('checkWallet', False)
|
|
|
+ if checkWallet and WithdrawGateway.is_ledger(source_key = self.paySubOrder.withdraw_source_key):
|
|
|
+ self.check_wallet(proxy, self.paySubOrder)
|
|
|
|
|
|
return proxy
|
|
|
|
|
|
- def create_refund_order(self):
|
|
|
- refundOrder = RefundMoneyRecord.issue(self.paySubOrder, self.refundFee, self.deductCoins)
|
|
|
+ def create_refund_order(self, **extraInfo):
|
|
|
+ refundOrder = RefundMoneyRecord.issue(
|
|
|
+ self.paySubOrder, self.refundFee, **extraInfo)
|
|
|
refundOrder.pay_sub_order = self.paySubOrder
|
|
|
return refundOrder
|
|
|
|
|
|
- def execute(self, frozen_callable, **kwargs):
|
|
|
+ def execute(self, frozen_callable, refund_callable, notify_url = None):
|
|
|
"""
|
|
|
执行退款的动作
|
|
|
对于经销商商户的流程: 检查 >> 建单 >> 扣除用户金额 >> 退款 >> 收到退款成功通知后建立负收益单和扣除经销商的记录金额
|
|
@@ -166,114 +191,54 @@ class RefundCash(object):
|
|
|
|
|
|
proxy = self.pre_check()
|
|
|
|
|
|
- payGateway = PaymentGateway.clone_from_order(self.payOrder) # type: PaymentGateway
|
|
|
-
|
|
|
try:
|
|
|
- refundOrder = self.create_refund_order() # type: RefundMoneyRecord
|
|
|
- except DuplicateKeyError:
|
|
|
+ refundOrder = self.create_refund_order(**self.extraInfo) # type: RefundMoneyRecord
|
|
|
+ except NotUniqueError:
|
|
|
raise UserServerException(u'已经有退款订单正在进行')
|
|
|
|
|
|
- if str(self.paySubOrder.id) == str(self.payOrder.id):
|
|
|
- logger.info(
|
|
|
- 'refund paras, orderNo = {} refundOrderNo = {} refundFee = {} totalFee = {}'.format(
|
|
|
- self.paySubOrder.orderNo, refundOrder.orderNo, self.refundFee, self.subTotalFee)
|
|
|
- )
|
|
|
- else:
|
|
|
- logger.info(
|
|
|
- 'refund paras, mix<orderNo = {}, totalFee={}>, sub<orderNo = {}, totalFee={}> '
|
|
|
- 'refundOrderNo = {} refundFee = {} '.format(
|
|
|
- self.payOrder.orderNo, self.totalFee, self.paySubOrder.orderNo, self.subTotalFee,
|
|
|
- refundOrder.orderNo, self.refundFee)
|
|
|
- )
|
|
|
+ logger.info('refund paras: {} {}'.format(refundOrder.orderNo, self.refund_paras))
|
|
|
+
|
|
|
+ split_map = proxy.partition_map
|
|
|
|
|
|
- refund_recharge_order = self.paySubOrder.new_refund_cash_order(refundOrder) # type: RechargeRecord
|
|
|
+ refund_income_order = self.paySubOrder.issue_refund_income_order(
|
|
|
+ refundOrder, split_map) # type: RechargeRecord
|
|
|
|
|
|
- frozen_callable(refundOrder, **kwargs) # 对资金实体进行退款(用户余额,卡余额等)
|
|
|
+ frozen_callable(refundOrder) # 对资金实体进行退款冻结(用户余额,卡余额等)
|
|
|
|
|
|
try:
|
|
|
- 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 = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费')
|
|
|
- except AliException as e:
|
|
|
- logger.info('refund failed , refund orderNo = {} reason = {}'.format(refundOrder.orderNo, e))
|
|
|
- raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
|
|
|
-
|
|
|
- if result["code"] != "10000":
|
|
|
- refundOrder.fail(errorCode = "{}-{}".format(result["code"], result.get("sub_code")),
|
|
|
- errorDesc = "{}-{}".format(result["msg"], result.get("sub_msg")))
|
|
|
-
|
|
|
- logger.info('ALIPAY Refund request successfully! return = {}'.format(result))
|
|
|
-
|
|
|
- elif payGateway.pay_app_type in [PayAppType.WECHAT, PayAppType.WECHAT_MINI]:
|
|
|
- try:
|
|
|
- result = payGateway.refund_to_user(
|
|
|
- out_trade_no = self.outTradeNo, out_refund_no = refundOrder.orderNo,
|
|
|
- refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费',
|
|
|
- notify_url = REFUND_NOTIFY_URL.WECHAT_REFUND_BACK)
|
|
|
- except WeChatPayException as e:
|
|
|
- logger.info('refund failed , refund orderNo = {} reason = {}'.format(refundOrder.orderNo, e))
|
|
|
- refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
|
|
|
- raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
|
|
|
-
|
|
|
- logger.info('WECHAT Refund request successfully! return = {}'.format(result))
|
|
|
-
|
|
|
- except UserServerException as se:
|
|
|
- logger.error(se.message)
|
|
|
- raise se
|
|
|
- except Exception as ee:
|
|
|
- # 这一步就不再更改订单的状态 由于不知道是退款前出错还是退款后出错 使用poll拉取订单状态来更新
|
|
|
- logger.exception(ee)
|
|
|
- raise UserServerException(ee.message)
|
|
|
+ self.submit_refund(
|
|
|
+ refundOrder, refund_income_order.partition_map, u'现金退款',
|
|
|
+ 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:
|
|
|
- # 资金池方式下,直接记录负单.所有对账时间都以系统内时间为准
|
|
|
- if payGateway.occupant.role == ROLE.agent:
|
|
|
+ refundOrder.reload()
|
|
|
+
|
|
|
+ if refundOrder.is_closed or refundOrder.is_success:
|
|
|
+ # 终态已经调用了post_pay, 所以不在做任何处理
|
|
|
+ pass
|
|
|
+
|
|
|
+ elif refundOrder.my_payment_gateway.occupant.role == ROLE.agent:
|
|
|
+ # 资金池情况下认为成功, 冻结运营商金额
|
|
|
+ refund_income_order.result = RechargeRecord.PayResult.SUCCESS
|
|
|
+ refund_income_order.finishedTime = datetime.datetime.now()
|
|
|
+ refund_income_order.save()
|
|
|
+
|
|
|
from apps.web.report.ledger import Ledger
|
|
|
- ledger = Ledger(USER_RECHARGE_TYPE.REFUND_CASH, refund_recharge_order)
|
|
|
- ledger.execute(journal = False, stats = True, check = False)
|
|
|
+ ledger = Ledger(refund_income_order.via, refund_income_order)
|
|
|
+ ledger.execute(stats = True)
|
|
|
|
|
|
return refundOrder
|
|
|
|
|
|
|
|
|
-class RetryRefundCash(object):
|
|
|
+class RetryRefundCash(RefundCashMixin):
|
|
|
def __init__(self, refundOrder): # type:(RefundMoneyRecord) -> None
|
|
|
- self.paySubOrder = refundOrder.pay_sub_order
|
|
|
- self.payOrder = self.paySubOrder.payOrder
|
|
|
-
|
|
|
- self.refundFee = refundOrder.money
|
|
|
- self.deductCoins = refundOrder.coins
|
|
|
+ super(RetryRefundCash, self).__init__(refundOrder.pay_sub_order, refundOrder.money)
|
|
|
|
|
|
self.refundOrder = refundOrder
|
|
|
- # 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
|
|
|
|
|
|
def pre_check(self):
|
|
|
"""
|
|
@@ -281,24 +246,26 @@ class RetryRefundCash(object):
|
|
|
:return:
|
|
|
"""
|
|
|
|
|
|
- if self.refundFee <= RMB(0) or self.refundFee > self.totalFee or self.refundFee > self.subTotalFee:
|
|
|
+ if self.refundFee <= RMB(0) or self.refundFee > self.subTotalFee:
|
|
|
raise ParameterError(u"退费金额错误")
|
|
|
|
|
|
- if self.deductCoins < VirtualCoin(
|
|
|
- 0) or self.deductCoins > self.totalCoins or self.deductCoins > self.subTotalCoins:
|
|
|
+ deductCoins = self.refundOrder.deductCoins
|
|
|
+ if deductCoins < VirtualCoin(0) or deductCoins > self.subTotalCoins:
|
|
|
raise ParameterError(u"扣除用户金币数目错误")
|
|
|
|
|
|
- if self.refundOrder.is_closed or self.refundOrder.is_success:
|
|
|
- raise UserServerException(u'已经完结订单不能重试')
|
|
|
+ if not self.refundOrder.is_fail and not self.refundOrder.is_no_order:
|
|
|
+ raise UserServerException(u'状态非错误的订单不能重试')
|
|
|
|
|
|
- proxy = DealerIncomeProxy.objects.filter(
|
|
|
- ref_id = self.paySubOrder.id).first() # type: DealerIncomeProxy
|
|
|
+ proxy = ClientDealerIncomeModelProxy.get_one(ref_id = self.paySubOrder.id)
|
|
|
if not proxy:
|
|
|
- raise UserServerException(u"订单尚未分账,无法退款")
|
|
|
+ raise UserServerException(u"订单尚未分账,无法退款(10002)")
|
|
|
+
|
|
|
+ if self.refundOrder.checkWallet and WithdrawGateway.is_ledger(source_key = self.paySubOrder.withdraw_source_key):
|
|
|
+ self.check_wallet(proxy, self.paySubOrder)
|
|
|
|
|
|
return proxy
|
|
|
|
|
|
- def execute(self, frozen_callable, **kwargs):
|
|
|
+ def execute(self, frozen_callable, refund_callable, notify_url = None):
|
|
|
"""
|
|
|
执行退款的动作
|
|
|
对于经销商商户的流程: 检查 >> 建单 >> 扣除用户金额 >> 退款 >> 收到退款成功通知后建立负收益单和扣除经销商的记录金额
|
|
@@ -308,126 +275,321 @@ class RetryRefundCash(object):
|
|
|
|
|
|
proxy = self.pre_check()
|
|
|
|
|
|
- payGateway = PaymentGateway.clone_from_order(self.payOrder) # type: PaymentGateway
|
|
|
-
|
|
|
- if str(self.paySubOrder.id) == str(self.payOrder.id):
|
|
|
- logger.info(
|
|
|
- 'retry refund paras, orderNo = {} refundOrderNo = {} refundFee = {} totalFee = {}'.format(
|
|
|
- self.paySubOrder.orderNo, self.refundOrder.orderNo, self.refundFee, self.subTotalFee)
|
|
|
- )
|
|
|
- else:
|
|
|
- logger.info(
|
|
|
- 'retry refund paras, mix<orderNo = {}, totalFee={}>, sub<orderNo = {}, totalFee={}> '
|
|
|
- 'refundOrderNo = {} refundFee = {} '.format(
|
|
|
- self.payOrder.orderNo, self.totalFee, self.paySubOrder.orderNo, self.subTotalFee,
|
|
|
- self.refundOrder.orderNo, self.refundFee)
|
|
|
- )
|
|
|
+ logger.info('retry refund paras: {} {}'.format(self.refundOrder.orderNo, self.refund_paras))
|
|
|
|
|
|
- puller = RefundManager().get_poller(payGateway.pay_app_type)
|
|
|
- puller(self.refundOrder).pull(payGateway, self.payOrder, refund_post_pay)
|
|
|
+ puller = RefundManager().get_poller(self.refundOrder.pay_app_type)
|
|
|
+ done = puller(self.refundOrder).pull(refund_post_pay)
|
|
|
+ if done:
|
|
|
+ return
|
|
|
|
|
|
self.refundOrder.reload()
|
|
|
|
|
|
- if self.refundOrder.is_success or self.refundOrder.is_closed:
|
|
|
- logger.debug('refund order {} has been finished.'.format(str(self.refundOrder)))
|
|
|
+ if not self.refundOrder.is_fail and not self.refundOrder.is_no_order:
|
|
|
+ logger.debug('refund order {} is not in fail status.'.format(str(self.refundOrder)))
|
|
|
return
|
|
|
|
|
|
- refund_order_record = self.refundOrder.refund_order_record
|
|
|
- if not refund_order_record:
|
|
|
- split_map = proxy.partition_map
|
|
|
- refund_order_record = self.paySubOrder.new_refund_cash_order(
|
|
|
+ if self.refundOrder.retryCount > 10:
|
|
|
+ matched = self.refundOrder.closed(errorCode = 'TIMEOUT', errorDesc = u'重试次数超限,退款失败')
|
|
|
+ if matched:
|
|
|
+ return refund_post_pay(self.refundOrder, False)
|
|
|
+
|
|
|
+ refund_income_order = self.refundOrder.refund_income_order
|
|
|
+ if not refund_income_order:
|
|
|
+ if 'billSplitOfOwner' in self.paySubOrder.attachParas: # 老的商户分账模式
|
|
|
+ if "billSplitList" in self.paySubOrder.attachParas:
|
|
|
+ owner_split = self.paySubOrder.attachParas['billSplitOfOwner']
|
|
|
+ owner_split['merchantId'] = owner_split.pop('splitBillMerchantEmail')
|
|
|
+ owner_split['money'] = owner_split.pop('splitBillAmount')
|
|
|
+
|
|
|
+ split_map = {
|
|
|
+ PARTITION_ROLE.OWNER: [owner_split],
|
|
|
+ PARTITION_ROLE.AGENT: [],
|
|
|
+ PARTITION_ROLE.PARTNER: []
|
|
|
+ }
|
|
|
+
|
|
|
+ for spliter in self.paySubOrder.attachParas['billSplitList']:
|
|
|
+ spliter['merchantId'] = spliter.pop('splitBillMerchantEmail')
|
|
|
+ spliter['money'] = spliter.pop('splitBillAmount')
|
|
|
+
|
|
|
+ if spliter['role'] == PARTITION_ROLE.AGENT:
|
|
|
+ split_map[PARTITION_ROLE.AGENT].append(spliter)
|
|
|
+ elif spliter['role'] == PARTITION_ROLE.PARTNER:
|
|
|
+ split_map[PARTITION_ROLE.PARTNER].append(spliter)
|
|
|
+ else:
|
|
|
+ raise UserServerException(u'错误的分账角色')
|
|
|
+ else:
|
|
|
+ owner_split = self.paySubOrder.attachParas['billSplitOfOwner']
|
|
|
+ owner_split['merchantId'] = owner_split.pop('splitBillMerchantEmail')
|
|
|
+ owner_split['money'] = owner_split.pop('splitBillAmount')
|
|
|
+
|
|
|
+ split_map = {
|
|
|
+ PARTITION_ROLE.OWNER: [owner_split],
|
|
|
+ PARTITION_ROLE.AGENT: [],
|
|
|
+ PARTITION_ROLE.PARTNER: []
|
|
|
+ }
|
|
|
+ else:
|
|
|
+ split_map = proxy.partition_map
|
|
|
+
|
|
|
+ refund_income_order = self.paySubOrder.issue_refund_income_order(
|
|
|
self.refundOrder, split_map) # type: RechargeRecord
|
|
|
|
|
|
- # 将订单的状态切换为 正在处理中
|
|
|
- self.refundOrder.processing()
|
|
|
+ succeed = self.refundOrder.retry_processing(
|
|
|
+ changeOrderNo = (not self.refundOrder.is_no_order) and self.refundOrder.pay_app_type in [PayAppType.JD_OPEN])
|
|
|
+ if not succeed:
|
|
|
+ logger.info(
|
|
|
+ 'refund ignored. refund orderNo = {} reason = unique check failure.'.format(self.refundOrder.orderNo))
|
|
|
+ return
|
|
|
|
|
|
- # 扣除实体的金额(用户或者实体卡)
|
|
|
- frozen_callable(self.refundOrder, **kwargs)
|
|
|
+ self.refundOrder.reload()
|
|
|
|
|
|
- try:
|
|
|
- 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 = self.refundOrder.orderNo,
|
|
|
- refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费')
|
|
|
- except AliException as e:
|
|
|
- logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo, e))
|
|
|
- raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
|
|
|
-
|
|
|
- if result["code"] != "10000":
|
|
|
- self.refundOrder.fail(errorCode = "{}-{}".format(result["code"], result.get("sub_code")),
|
|
|
- errorDesc = "{}-{}".format(result["msg"], result.get("sub_msg")))
|
|
|
-
|
|
|
- logger.info('ALIPAY Refund request successfully! return = {}'.format(result))
|
|
|
-
|
|
|
- elif payGateway.pay_app_type == PayAppType.WECHAT:
|
|
|
- try:
|
|
|
- result = payGateway.refund_to_user(
|
|
|
- out_trade_no = self.outTradeNo, out_refund_no = self.refundOrder.orderNo,
|
|
|
- refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费',
|
|
|
- notify_url = REFUND_NOTIFY_URL.WECHAT_REFUND_BACK)
|
|
|
- except WeChatPayException as e:
|
|
|
- logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo, e))
|
|
|
- self.refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
|
|
|
- raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
|
|
|
-
|
|
|
- logger.info('WECHAT Refund request successfully! return = {}'.format(result))
|
|
|
- else:
|
|
|
- self.refundOrder.fail(errorDesc = u"不支持的退款模式")
|
|
|
- raise UserServerException(u"不支持的退款模式")
|
|
|
+ frozen_callable(self.refundOrder)
|
|
|
|
|
|
- except UserServerException as se:
|
|
|
- logger.error(se.message)
|
|
|
- raise se
|
|
|
- except Exception as ee:
|
|
|
- # 这一步就不再更改订单的状态 由于不知道是退款前出错还是退款后出错 使用poll拉取订单状态来更新
|
|
|
- logger.exception(ee)
|
|
|
- raise UserServerException(ee.message)
|
|
|
+ try:
|
|
|
+ self.submit_refund(
|
|
|
+ self.refundOrder, refund_income_order.partition_map, u'现金退款',
|
|
|
+ notify_url or self.refundOrder.notify_url, refund_callable)
|
|
|
+ except Exception:
|
|
|
+ import traceback
|
|
|
+ logger.warning(
|
|
|
+ 'Refund request failure! orderNo = {}; e = {}'.format(self.refundOrder, traceback.format_exc()))
|
|
|
|
|
|
finally:
|
|
|
- # 资金池方式下,直接记录负单.所有对账时间都以系统内时间为准
|
|
|
- if payGateway.occupant.role == ROLE.agent:
|
|
|
- from apps.web.report.ledger import Ledger
|
|
|
- ledger = Ledger(USER_RECHARGE_TYPE.REFUND_CASH, refund_order_record)
|
|
|
- ledger.execute(journal = False, stats = True, check = False)
|
|
|
+ self.refundOrder.reload()
|
|
|
+
|
|
|
+ if self.refundOrder.is_closed or self.refundOrder.is_success:
|
|
|
+ # 终态已经调用了post_pay, 所以不在做任何处理
|
|
|
+ pass
|
|
|
+
|
|
|
+ elif self.refundOrder.my_payment_gateway.occupant.role == ROLE.agent:
|
|
|
+ # 资金池情况下认为成功, 冻结运营商金额
|
|
|
+ if refund_income_order.result != RechargeRecord.PayResult.SUCCESS:
|
|
|
+ refund_income_order.result = RechargeRecord.PayResult.SUCCESS
|
|
|
+ refund_income_order.finishedTime = datetime.datetime.now()
|
|
|
+ refund_income_order.save()
|
|
|
+
|
|
|
+ if not refund_income_order.is_ledgered:
|
|
|
+ from apps.web.report.ledger import Ledger
|
|
|
+ ledger = Ledger(USER_RECHARGE_TYPE.REFUND_CASH, refund_income_order)
|
|
|
+ ledger.execute(journal = False, stats = True, check = False)
|
|
|
|
|
|
return self.refundOrder
|
|
|
|
|
|
|
|
|
-def refund_post_pay(refundOrder, finishedTime):
|
|
|
- # type: (RefundMoneyRecord, datetime)->None
|
|
|
+def refund_post_pay(refundOrder, success):
|
|
|
+ # type: (RefundMoneyRecord, bool)->None
|
|
|
+ try:
|
|
|
+ refund_income_order = refundOrder.refund_income_order # type: RechargeRecord
|
|
|
|
|
|
- refundOrder.user.commit_refund_cash(refundOrder)
|
|
|
+ try:
|
|
|
+ refPay = refund_income_order.extraInfo['refPay']
|
|
|
+ if isinstance(refund_income_order.extraInfo['refPay'], dict):
|
|
|
+ refund_income_order.extraInfo['refPay'] = ObjectId(refPay.pop('objId'))
|
|
|
+ refund_income_order.save()
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ if success:
|
|
|
+ refund_success_callback(refundOrder, refundOrder.finishedTime, refund_income_order)
|
|
|
+ else:
|
|
|
+ refund_fail_callback(refundOrder, refundOrder.finishedTime, refund_income_order)
|
|
|
+ except Exception:
|
|
|
+ import traceback
|
|
|
+ logger.warning(
|
|
|
+ 'Refund callback failure. orderNo = {}; e = {}'.format(refundOrder.orderNo, traceback.format_exc()))
|
|
|
|
|
|
- refund_order_record = refundOrder.refund_order_record # type: RechargeRecord
|
|
|
|
|
|
- refund_order_record.finishedTime = finishedTime
|
|
|
- refund_order_record.result = RechargeRecord.PayResult.SUCCESS
|
|
|
- refund_order_record.save()
|
|
|
+def refund_success_callback(refundOrder, finishedTime, refund_income_order):
|
|
|
+ # type: (RefundMoneyRecord, datetime, RechargeRecord)->None
|
|
|
+
|
|
|
+ rechargeOrder = refundOrder.pay_sub_order
|
|
|
+
|
|
|
+ if rechargeOrder.via in [USER_RECHARGE_TYPE.RECHARGE, USER_RECHARGE_TYPE.RECHARGE_CASH]:
|
|
|
+ refundOrder.user.commit_refund_cash(refundOrder)
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_CARD:
|
|
|
+ if rechargeOrder.attachParas.get('terminalRecharge', False):
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ card = Card.objects(id = rechargeOrder.attachParas['cardId']).first() # type: Card
|
|
|
+ card.clear_frozen_balance(card.freeze_transaction_id('r'), VirtualCoin(0))
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_VIRTUAL_CARD:
|
|
|
+ userVirtualCard = UserVirtualCard.objects(id = rechargeOrder.attachParas['cardId']).first()
|
|
|
+ userVirtualCard.commit_refund(refundOrder)
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_MONTHLY_PACKAGE:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ logger.debug('via({}) is not support.'.format(rechargeOrder.via))
|
|
|
+ return
|
|
|
+
|
|
|
+ if not refund_income_order.is_ledgered: # 记录收益
|
|
|
+ refund_income_order.finishedTime = finishedTime
|
|
|
+ refund_income_order.result = RechargeRecord.PayResult.SUCCESS
|
|
|
+ refund_income_order.save()
|
|
|
|
|
|
- # 记录资金池的变动
|
|
|
- if not refund_order_record.is_ledgered:
|
|
|
from apps.web.report.ledger import Ledger
|
|
|
- ledger = Ledger(DEALER_INCOME_SOURCE.REFUND_CASH, refund_order_record)
|
|
|
+ ledger = Ledger(refund_income_order.via, refund_income_order)
|
|
|
ledger.execute(stats=True)
|
|
|
+ else:
|
|
|
+ # FIX预扣单状态.不是SUCCESS先改成SUCCESS
|
|
|
+ if refund_income_order.result != RechargeRecord.PayResult.SUCCESS:
|
|
|
+ refund_income_order.finishedTime = refund_income_order.dateTimeAdded
|
|
|
+ refund_income_order.result = RechargeRecord.PayResult.SUCCESS
|
|
|
+ refund_income_order.save()
|
|
|
+
|
|
|
+ for item in rechargeOrder.extraInfo['refRefund']:
|
|
|
+ if 'deductId' in item:
|
|
|
+ if str(item['deductId']) == str(refund_income_order.id):
|
|
|
+ item['status'] = RefundMoneyRecord.Status.SUCCESS
|
|
|
+ item['finishedTime'] = refundOrder.finishedTime
|
|
|
+ rechargeOrder.save()
|
|
|
+ break
|
|
|
+ elif str(item['objId']) == str(refund_income_order.id):
|
|
|
+ item['status'] = RefundMoneyRecord.Status.SUCCESS
|
|
|
+ item['deductId'] = ObjectId(item.pop('objId'))
|
|
|
+ item['finishedTime'] = refundOrder.finishedTime
|
|
|
+ rechargeOrder.save()
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+def refund_fail_callback(refundOrder, finishedTime, refund_income_order):
|
|
|
+ rechargeOrder = refundOrder.pay_sub_order
|
|
|
+
|
|
|
+ if rechargeOrder.via in [USER_RECHARGE_TYPE.RECHARGE, USER_RECHARGE_TYPE.RECHARGE_CASH]:
|
|
|
+ user = refundOrder.pay_sub_order.myuser # type: MyUser
|
|
|
+ user.revoke_refund_cash(refundOrder)
|
|
|
+
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_CARD:
|
|
|
+ if rechargeOrder.attachParas.get('terminalRecharge', False):
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ card = Card.objects(id = rechargeOrder.attachParas['cardId']).first()
|
|
|
+ if not card:
|
|
|
+ raise UserServerException(u'充值卡不存在')
|
|
|
|
|
|
+ card.recover_frozen_balance(transaction_id = card.freeze_transaction_id('r'), fee = refundOrder.deductCoins)
|
|
|
|
|
|
-def frozen_refund_for_balance(refundOrder, user = None, minus_total_consume = VirtualCoin(0)):
|
|
|
- # type: (RefundMoneyRecord, MyUser, VirtualCoin)->bool
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_VIRTUAL_CARD:
|
|
|
+ userVirtualCard = UserVirtualCard.objects(id = rechargeOrder.attachParas['cardId']).first()
|
|
|
+ if not userVirtualCard:
|
|
|
+ raise UserServerException(u'虚拟卡不存在')
|
|
|
+
|
|
|
+ userVirtualCard.revoke_refund(refundOrder)
|
|
|
+
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_MONTHLY_PACKAGE:
|
|
|
+ monthlyPackage = MonthlyPackage.objects(id = rechargeOrder.attachParas['cardId']).first()
|
|
|
+ if not monthlyPackage:
|
|
|
+ raise UserServerException(u'包月套餐不存在')
|
|
|
+
|
|
|
+ monthlyPackage.toggle_disable(isDisable = 0)
|
|
|
+
|
|
|
+ else:
|
|
|
+ raise UserServerException(u'不支持的退款订单类型')
|
|
|
+
|
|
|
+ if refund_income_order.is_ledgered:
|
|
|
+ # 如果已经扣款分账则建立一个退单收益单
|
|
|
+
|
|
|
+ # FIX预扣单状态.不是SUCCESS先改成SUCCESS
|
|
|
+ if refund_income_order.result != RechargeRecord.PayResult.SUCCESS:
|
|
|
+ refund_income_order.finishedTime = refund_income_order.dateTimeAdded
|
|
|
+ refund_income_order.result = RechargeRecord.PayResult.SUCCESS
|
|
|
+ refund_income_order.save()
|
|
|
+
|
|
|
+ revoke_income_order = refund_income_order.issue_refund_revoke_order()
|
|
|
+
|
|
|
+ from apps.web.report.ledger import Ledger
|
|
|
+ ledger = Ledger(DEALER_INCOME_SOURCE.REVOKE_REFUND_CASH, revoke_income_order)
|
|
|
+ ledger.execute(journal = False, stats = True, check = False)
|
|
|
+
|
|
|
+ for item in rechargeOrder.extraInfo['refRefund']:
|
|
|
+ if 'deductId' in item:
|
|
|
+ if str(item['deductId']) == str(refund_income_order.id):
|
|
|
+ item['status'] = RefundMoneyRecord.Status.CLOSED
|
|
|
+ item['revokeId'] = revoke_income_order.id
|
|
|
+ item['finishedTime'] = refundOrder.finishedTime
|
|
|
+ rechargeOrder.save()
|
|
|
+ break
|
|
|
+ elif str(item['objId']) == str(refund_income_order.id):
|
|
|
+ item['status'] = RefundMoneyRecord.Status.CLOSED
|
|
|
+ item['deductId'] = ObjectId(item.pop('objId'))
|
|
|
+ item['revokeId'] = revoke_income_order.id
|
|
|
+ item['finishedTime'] = refundOrder.finishedTime
|
|
|
+ rechargeOrder.save()
|
|
|
+ break
|
|
|
|
|
|
- if user:
|
|
|
- if user.openId != refundOrder.pay_sub_order.openId:
|
|
|
- raise UserServerException(u"用户参数错误")
|
|
|
- else:
|
|
|
- if user.groupId != refundOrder.pay_sub_order.groupId:
|
|
|
- user = refundOrder.pay_sub_order.user
|
|
|
else:
|
|
|
- user = refundOrder.pay_sub_order.user
|
|
|
+ refund_income_order.finishedTime = finishedTime
|
|
|
+ refund_income_order.result = RechargeRecord.PayResult.CANCEL
|
|
|
+ refund_income_order.save()
|
|
|
+
|
|
|
+ for item in rechargeOrder.extraInfo['refRefund']:
|
|
|
+ if 'deductId' in item:
|
|
|
+ if str(item['deductId']) == str(refund_income_order.id):
|
|
|
+ item['status'] = RefundMoneyRecord.Status.CLOSED
|
|
|
+ item['finishedTime'] = refundOrder.finishedTime
|
|
|
+ rechargeOrder.save()
|
|
|
+ break
|
|
|
+ elif str(item['objId']) == str(refund_income_order.id):
|
|
|
+ item['status'] = RefundMoneyRecord.Status.CLOSED
|
|
|
+ item['deductId'] = item.pop('objId')
|
|
|
+ item['finishedTime'] = refundOrder.finishedTime
|
|
|
+ rechargeOrder.save()
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+def frozen_refund_func(refundOrder):
|
|
|
+ # type: (RefundMoneyRecord)->None
|
|
|
+
|
|
|
+ """
|
|
|
+ :param refundOrder:
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+
|
|
|
+ rechargeOrder = refundOrder.pay_sub_order
|
|
|
+
|
|
|
+ if rechargeOrder.via in [USER_RECHARGE_TYPE.RECHARGE, USER_RECHARGE_TYPE.RECHARGE_CASH]:
|
|
|
+ user = refundOrder.pay_sub_order.myuser
|
|
|
+
|
|
|
+ if not user:
|
|
|
+ raise UserServerException(u'用户不存在')
|
|
|
+
|
|
|
+ minus_total_consume = VirtualCoin(refundOrder.extraInfo.get('minus_total_consume', 0))
|
|
|
|
|
|
- if not user:
|
|
|
- raise UserServerException(u'用户不存在')
|
|
|
+ deduct_coins = refundOrder.deductCoins
|
|
|
+ frozen_coins = refundOrder.frozenCoins
|
|
|
|
|
|
- return user.prepare_refund_cash(refundOrder, minus_total_consume)
|
|
|
+ logger.debug('MyUser<id={}> prepare refund cash. money = {}, coins = {}, before = {}'.format(
|
|
|
+ str(user.id), refundOrder.money, deduct_coins, user.balance
|
|
|
+ ))
|
|
|
+
|
|
|
+ user.prepare_refund_cash(refundOrder, deduct_coins, frozen_coins, minus_total_consume)
|
|
|
+
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_CARD:
|
|
|
+ if rechargeOrder.attachParas.get('terminalRecharge', False):
|
|
|
+ logger.debug('Card<id={}> prepare refund cash. money = {}'.format(
|
|
|
+ 'terminalRecharge', refundOrder.money
|
|
|
+ ))
|
|
|
+ else:
|
|
|
+ card = Card.objects(id = rechargeOrder.attachParas['cardId']).first()
|
|
|
+ if not card:
|
|
|
+ raise UserServerException(u'充值卡不存在')
|
|
|
+
|
|
|
+ logger.debug('Card<id={}> prepare refund cash. money = {}, coins = {}, before = {}'.format(
|
|
|
+ str(card.id), refundOrder.money, refundOrder.deductCoins, card.balance
|
|
|
+ ))
|
|
|
+
|
|
|
+ card.freeze_balance(transaction_id = card.freeze_transaction_id('r'), fee = refundOrder.deductCoins)
|
|
|
+
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_VIRTUAL_CARD:
|
|
|
+ userVirtualCard = UserVirtualCard.objects(id=rechargeOrder.attachParas['cardId']).first()
|
|
|
+ if not userVirtualCard:
|
|
|
+ raise UserServerException(u'虚拟卡不存在')
|
|
|
+
|
|
|
+ userVirtualCard.prepare_refund(refundOrder)
|
|
|
+
|
|
|
+ elif rechargeOrder.via == USER_RECHARGE_TYPE.RECHARGE_MONTHLY_PACKAGE:
|
|
|
+ monthlyPackage = MonthlyPackage.objects(id = rechargeOrder.attachParas['cardId']).first()
|
|
|
+ if not monthlyPackage:
|
|
|
+ raise UserServerException(u'包月套餐不存在')
|
|
|
+
|
|
|
+ monthlyPackage.toggle_disable(isDisable = 1)
|
|
|
+
|
|
|
+ else:
|
|
|
+ logger.debug('via({}) is not support.'.format(rechargeOrder.via))
|