transaction_deprecated.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import time
  6. import uuid
  7. from pymongo.errors import DuplicateKeyError
  8. from typing import TYPE_CHECKING, Dict, Union, Any
  9. from apilib.monetary import VirtualCoin, RMB
  10. from apps.web.common.transaction import OrderNoMaker, OrderMainType, RefundSubType
  11. from apps.web.common.transaction.pay import RefundManager
  12. from apps.web.constant import USER_RECHARGE_TYPE, PARTITION_ROLE, RechargeRecordVia
  13. from apps.web.core import PayAppType, ROLE
  14. from apps.web.core.exceptions import ParameterError
  15. from apps.web.core.payment import PaymentGateway
  16. from apps.web.dealer.define import DEALER_INCOME_SOURCE
  17. from apps.web.dealer.proxy import DealerIncomeProxy
  18. from apps.web.device.models import Group
  19. from apps.web.exceptions import UserServerException
  20. from apps.web.user.conf import REFUND_NOTIFY_URL
  21. from apps.web.user.models import MyUser, RechargeRecord, RefundMoneyRecord
  22. from library.alipay import AliException
  23. from library.jd.exceptions import JDPayException
  24. from library.jdopen import JdOpenException
  25. from library.wechatbase.exceptions import WeChatPayException
  26. logger = logging.getLogger(__name__)
  27. if TYPE_CHECKING:
  28. pass
  29. def refund_money(device, money, openId):
  30. if VirtualCoin(money) <= VirtualCoin(0):
  31. logger.debug('{} is zero. not to refund.'.format(VirtualCoin(money)))
  32. return
  33. groupId = device['groupId']
  34. group = Group.get_group(groupId)
  35. if group.is_free:
  36. logger.info('group(%s) is set to free, refund_money will not proceed' % (device['groupId'],))
  37. return
  38. user = MyUser.objects(openId = openId, groupId = groupId).get() # type: MyUser
  39. user.refund(VirtualCoin(money))
  40. # 添加一条充值记录
  41. orderNo = str(uuid.uuid4())
  42. try:
  43. newRcd = RechargeRecord(orderNo = orderNo,
  44. coins = money, openId = openId, groupId = device['groupId'],
  45. devNo = device['devNo'], logicalCode = device['logicalCode'],
  46. ownerId = device['ownerId'],
  47. groupName = group['groupName'], groupNumber = device['groupNumber'],
  48. address = group['address'], wxOrderNo = u'设备退币',
  49. devTypeName = device.devTypeName, nickname = '',
  50. result = 'success', via = 'refund')
  51. newRcd.save()
  52. except Exception, e:
  53. logger.exception('update record for feedback coins error=%s,orderNo=%s' % (e, orderNo))
  54. def refund_cash(recharge_record, refundFee, deductCoins, **kwargs):
  55. # type:(RechargeRecord, RMB, VirtualCoin, Dict[str, Any])->RefundMoneyRecord
  56. """
  57. 新的执行退款 为了保持导包顺序不变
  58. :param deductCoins:
  59. :param recharge_record:
  60. :param refundFee:
  61. :param kwargs 用户为资金实体的情况下, 传入user和minus_total_consume参数
  62. :return:
  63. """
  64. if recharge_record.via in [RechargeRecordVia.Balance, RechargeRecordVia.Cash, RechargeRecordVia.StartDevice]:
  65. return RefundCash(recharge_record, refundFee, deductCoins).execute(
  66. frozen_callable = frozen_refund_for_balance, **kwargs)
  67. else:
  68. raise UserServerException(u'不支持该类型订单退款')
  69. class RefundCash(object):
  70. # 最长的查询分账时间
  71. MAX_LEDGER_CHECK_TIME = 15
  72. def __init__(self, rechargeOrder, refundFee, deductCoins): # type:(RechargeRecord, RMB, VirtualCoin) -> None
  73. self.paySubOrder = rechargeOrder
  74. self.payOrder = self.paySubOrder.payOrder
  75. self.refundFee = refundFee
  76. self.deductCoins = deductCoins
  77. # self._nextSeq = 1
  78. @property
  79. def outTradeNo(self):
  80. """
  81. 交易单号
  82. :return:
  83. """
  84. return self.payOrder.orderNo
  85. @property
  86. def totalFee(self):
  87. return self.payOrder.money
  88. @property
  89. def totalCoins(self):
  90. return self.payOrder.coins
  91. @property
  92. def subTotalFee(self):
  93. return self.paySubOrder.money
  94. @property
  95. def subTotalCoins(self):
  96. return self.paySubOrder.coins
  97. def pre_check(self):
  98. """
  99. 退款的预检查
  100. :return:
  101. """
  102. if self.refundFee <= RMB(0) or self.refundFee > self.totalFee or self.refundFee > self.subTotalFee:
  103. raise ParameterError(u"退费金额错误")
  104. if self.deductCoins < VirtualCoin(
  105. 0) or self.deductCoins > self.totalCoins or self.deductCoins > self.subTotalCoins:
  106. raise ParameterError(u"扣除用户金币数目错误")
  107. check_end_time = int(time.time()) + self.MAX_LEDGER_CHECK_TIME
  108. if not self.paySubOrder.is_success():
  109. raise UserServerException(u'非成功订单无法进行退款')
  110. while not self.paySubOrder.is_ledgered and int(time.time()) < check_end_time:
  111. logger.debug('{} is not allocated. wait to be allocated.'.format(repr(self.paySubOrder)))
  112. # TODO 考虑回调的方式进行
  113. time.sleep(5)
  114. self.paySubOrder.reload()
  115. proxy = DealerIncomeProxy.objects.filter(
  116. ref_id = self.paySubOrder.id).first() # type: DealerIncomeProxy
  117. if not proxy:
  118. raise UserServerException(u"订单尚未分账,无法退款")
  119. return proxy
  120. def create_refund_order(self):
  121. refundOrder = RefundMoneyRecord.issue(self.paySubOrder, self.refundFee, self.deductCoins)
  122. refundOrder.pay_sub_order = self.paySubOrder
  123. return refundOrder
  124. def execute(self, frozen_callable, **kwargs):
  125. """
  126. 执行退款的动作
  127. 对于经销商商户的流程: 检查 >> 建单 >> 扣除用户金额 >> 退款 >> 收到退款成功通知后建立负收益单和扣除经销商的记录金额
  128. 对于资金池商的流程: 检查 >> 建单 >> 扣除用户金额 >> 建立负单和扣除经销商的记录金额 >> 退款
  129. :return:
  130. """
  131. proxy = self.pre_check()
  132. payGateway = PaymentGateway.clone_from_order(self.payOrder) # type: PaymentGateway
  133. try:
  134. refundOrder = self.create_refund_order() # type: RefundMoneyRecord
  135. except DuplicateKeyError:
  136. raise UserServerException(u'已经有退款订单正在进行')
  137. if str(self.paySubOrder.id) == str(self.payOrder.id):
  138. logger.info(
  139. 'refund paras, orderNo = {} refundOrderNo = {} refundFee = {} totalFee = {}'.format(
  140. self.paySubOrder.orderNo, refundOrder.orderNo, self.refundFee, self.subTotalFee)
  141. )
  142. else:
  143. logger.info(
  144. 'refund paras, mix<orderNo = {}, totalFee={}>, sub<orderNo = {}, totalFee={}> '
  145. 'refundOrderNo = {} refundFee = {} '.format(
  146. self.payOrder.orderNo, self.totalFee, self.paySubOrder.orderNo, self.subTotalFee,
  147. refundOrder.orderNo, self.refundFee)
  148. )
  149. refund_recharge_order = self.paySubOrder.new_refund_cash_order(refundOrder) # type: RechargeRecord
  150. frozen_callable(refundOrder, **kwargs) # 对资金实体进行退款(用户余额,卡余额等)
  151. try:
  152. if payGateway.pay_app_type == PayAppType.ALIPAY:
  153. # 支付宝的退款方式
  154. # 支付宝的退款很特殊,接口状态以及业务状态均在同步接口中返回 其中 code = 10000 表示接口成功 即申请成功了 fund_change= Y 表示退款成功
  155. # 而当接口状态成功 code=10000 但是资金未发生变动 fund_change=N 的时候,则退款是不成功的(最好需要轮询一次),此时不改变退款单的状态
  156. try:
  157. result = payGateway.refund_to_user(
  158. out_trade_no = self.outTradeNo, out_refund_no = refundOrder.orderNo,
  159. refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费')
  160. except AliException as e:
  161. logger.info('refund failed , refund orderNo = {} reason = {}'.format(refundOrder.orderNo, e))
  162. raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
  163. if result["code"] != "10000":
  164. refundOrder.fail(errorCode = "{}-{}".format(result["code"], result.get("sub_code")),
  165. errorDesc = "{}-{}".format(result["msg"], result.get("sub_msg")))
  166. logger.info('ALIPAY Refund request successfully! return = {}'.format(result))
  167. elif payGateway.pay_app_type in [PayAppType.WECHAT, PayAppType.WECHAT_MINI]:
  168. try:
  169. result = payGateway.refund_to_user(
  170. out_trade_no = self.outTradeNo, out_refund_no = refundOrder.orderNo,
  171. refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费',
  172. notify_url = REFUND_NOTIFY_URL.WECHAT_REFUND_BACK)
  173. except WeChatPayException as e:
  174. logger.info('refund failed , refund orderNo = {} reason = {}'.format(refundOrder.orderNo, e))
  175. refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
  176. raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
  177. logger.info('WECHAT Refund request successfully! return = {}'.format(result))
  178. except UserServerException as se:
  179. logger.error(se.message)
  180. raise se
  181. except Exception as ee:
  182. # 这一步就不再更改订单的状态 由于不知道是退款前出错还是退款后出错 使用poll拉取订单状态来更新
  183. logger.exception(ee)
  184. raise UserServerException(ee.message)
  185. finally:
  186. # 资金池方式下,直接记录负单.所有对账时间都以系统内时间为准
  187. if payGateway.occupant.role == ROLE.agent:
  188. from apps.web.report.ledger import Ledger
  189. ledger = Ledger(USER_RECHARGE_TYPE.REFUND_CASH, refund_recharge_order)
  190. ledger.execute(journal = False, stats = True, check = False)
  191. return refundOrder
  192. class RetryRefundCash(object):
  193. def __init__(self, refundOrder): # type:(RefundMoneyRecord) -> None
  194. self.paySubOrder = refundOrder.pay_sub_order
  195. self.payOrder = self.paySubOrder.payOrder
  196. self.refundFee = refundOrder.money
  197. self.deductCoins = refundOrder.coins
  198. self.refundOrder = refundOrder
  199. # self._nextSeq = 1
  200. @property
  201. def outTradeNo(self):
  202. """
  203. 交易单号
  204. :return:
  205. """
  206. return self.payOrder.orderNo
  207. @property
  208. def totalFee(self):
  209. return self.payOrder.money
  210. @property
  211. def totalCoins(self):
  212. return self.payOrder.coins
  213. @property
  214. def subTotalFee(self):
  215. return self.paySubOrder.money
  216. @property
  217. def subTotalCoins(self):
  218. return self.paySubOrder.coins
  219. def pre_check(self):
  220. """
  221. 退款的预检查
  222. :return:
  223. """
  224. if self.refundFee <= RMB(0) or self.refundFee > self.totalFee or self.refundFee > self.subTotalFee:
  225. raise ParameterError(u"退费金额错误")
  226. if self.deductCoins < VirtualCoin(
  227. 0) or self.deductCoins > self.totalCoins or self.deductCoins > self.subTotalCoins:
  228. raise ParameterError(u"扣除用户金币数目错误")
  229. if self.refundOrder.is_closed or self.refundOrder.is_success:
  230. raise UserServerException(u'已经完结订单不能重试')
  231. proxy = DealerIncomeProxy.objects.filter(
  232. ref_id = self.paySubOrder.id).first() # type: DealerIncomeProxy
  233. if not proxy:
  234. raise UserServerException(u"订单尚未分账,无法退款")
  235. return proxy
  236. def execute(self, frozen_callable, **kwargs):
  237. """
  238. 执行退款的动作
  239. 对于经销商商户的流程: 检查 >> 建单 >> 扣除用户金额 >> 退款 >> 收到退款成功通知后建立负收益单和扣除经销商的记录金额
  240. 对于资金池商的流程: 检查 >> 建单 >> 扣除用户金额 >> 建立负单和扣除经销商的记录金额 >> 退款
  241. :return:
  242. """
  243. proxy = self.pre_check()
  244. payGateway = PaymentGateway.clone_from_order(self.payOrder) # type: PaymentGateway
  245. if str(self.paySubOrder.id) == str(self.payOrder.id):
  246. logger.info(
  247. 'retry refund paras, orderNo = {} refundOrderNo = {} refundFee = {} totalFee = {}'.format(
  248. self.paySubOrder.orderNo, self.refundOrder.orderNo, self.refundFee, self.subTotalFee)
  249. )
  250. else:
  251. logger.info(
  252. 'retry refund paras, mix<orderNo = {}, totalFee={}>, sub<orderNo = {}, totalFee={}> '
  253. 'refundOrderNo = {} refundFee = {} '.format(
  254. self.payOrder.orderNo, self.totalFee, self.paySubOrder.orderNo, self.subTotalFee,
  255. self.refundOrder.orderNo, self.refundFee)
  256. )
  257. puller = RefundManager().get_poller(payGateway.pay_app_type)
  258. puller(self.refundOrder).pull(payGateway, self.payOrder, refund_post_pay)
  259. self.refundOrder.reload()
  260. if self.refundOrder.is_success or self.refundOrder.is_closed:
  261. logger.debug('refund order {} has been finished.'.format(str(self.refundOrder)))
  262. return
  263. refund_order_record = self.refundOrder.refund_order_record
  264. if not refund_order_record:
  265. if 'billSplitOfOwner' in self.paySubOrder.attachParas: # 老的商户分账模式
  266. if "billSplitList" in self.paySubOrder.attachParas:
  267. owner_split = self.paySubOrder.attachParas['billSplitOfOwner']
  268. owner_split['merchantId'] = owner_split.pop('splitBillMerchantEmail')
  269. owner_split['money'] = owner_split.pop('splitBillAmount')
  270. split_map = {
  271. PARTITION_ROLE.OWNER: [owner_split],
  272. PARTITION_ROLE.AGENT: [],
  273. PARTITION_ROLE.PARTNER: []
  274. }
  275. for spliter in self.paySubOrder.attachParas['billSplitList']:
  276. spliter['merchantId'] = spliter.pop('splitBillMerchantEmail')
  277. spliter['money'] = spliter.pop('splitBillAmount')
  278. if spliter['role'] == PARTITION_ROLE.AGENT:
  279. split_map[PARTITION_ROLE.AGENT].append(spliter)
  280. elif spliter['role'] == PARTITION_ROLE.PARTNER:
  281. split_map[PARTITION_ROLE.PARTNER].append(spliter)
  282. else:
  283. raise UserServerException(u'错误的分账角色')
  284. else:
  285. owner_split = self.paySubOrder.attachParas['billSplitOfOwner']
  286. owner_split['merchantId'] = owner_split.pop('splitBillMerchantEmail')
  287. owner_split['money'] = owner_split.pop('splitBillAmount')
  288. split_map = {
  289. PARTITION_ROLE.OWNER: [owner_split],
  290. PARTITION_ROLE.AGENT: [],
  291. PARTITION_ROLE.PARTNER: []
  292. }
  293. else:
  294. split_map = proxy.partition_map
  295. refund_order_record = self.paySubOrder.new_refund_cash_order(
  296. self.refundOrder, split_map) # type: RechargeRecord
  297. # 将订单的状态切换为 正在处理中
  298. self.refundOrder.processing()
  299. # 扣除实体的金额(用户或者实体卡)
  300. frozen_callable(self.refundOrder, **kwargs)
  301. # self.deduct_from_refund_order(self.refundOrder)
  302. try:
  303. if payGateway.pay_app_type == PayAppType.ALIPAY:
  304. # 支付宝的退款方式
  305. # 支付宝的退款很特殊,接口状态以及业务状态均在同步接口中返回 其中 code = 10000 表示接口成功 即申请成功了 fund_change= Y 表示退款成功
  306. # 而当接口状态成功 code=10000 但是资金未发生变动 fund_change=N 的时候,则退款是不成功的(最好需要轮询一次),此时不改变退款单的状态
  307. try:
  308. result = payGateway.refund_to_user(
  309. out_trade_no = self.outTradeNo, out_refund_no = self.refundOrder.orderNo,
  310. refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费')
  311. except AliException as e:
  312. logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo, e))
  313. raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
  314. if result["code"] != "10000":
  315. self.refundOrder.fail(errorCode = "{}-{}".format(result["code"], result.get("sub_code")),
  316. errorDesc = "{}-{}".format(result["msg"], result.get("sub_msg")))
  317. logger.info('ALIPAY Refund request successfully! return = {}'.format(result))
  318. elif payGateway.pay_app_type == PayAppType.WECHAT:
  319. try:
  320. result = payGateway.refund_to_user(
  321. out_trade_no = self.outTradeNo, out_refund_no = self.refundOrder.orderNo,
  322. refund_fee = self.refundFee, total_fee = self.totalFee, refund_reason = u'退费',
  323. notify_url = REFUND_NOTIFY_URL.WECHAT_REFUND_BACK)
  324. except WeChatPayException as e:
  325. logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo, e))
  326. self.refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
  327. raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
  328. logger.info('WECHAT Refund request successfully! return = {}'.format(result))
  329. elif payGateway.pay_app_type == PayAppType.JD_AGGR:
  330. try:
  331. result = payGateway.refund_to_user(
  332. out_trade_no = self.outTradeNo, out_refund_no = self.refundOrder.orderNo,
  333. refund_fee = self.refundFee,
  334. total_fee = self.totalFee, refund_reason = u'退费',
  335. refund_url = REFUND_NOTIFY_URL.WECHAT_REFUND_BACK,
  336. billSplitList = payGateway.refund_bill_split_list(refund_order_record.partition_map))
  337. except JDPayException as e:
  338. logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo, e))
  339. self.refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
  340. raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
  341. logger.info('JDAGGRE Refund request successfully! return = {}'.format(result))
  342. elif payGateway.pay_app_type == PayAppType.RCU:
  343. # RUC 河南农村信用社 这个暂时不修改 不做回调处理 申请成功即认为成功
  344. result = payGateway.refund_to_user(
  345. out_trade_no = self.outTradeNo, out_refund_no = self.refundOrder.orderNo,
  346. refund_fee = self.refundFee,
  347. total_fee = self.totalFee, refund_reason = u'退费')
  348. if result['TransStatus'] not in ['1', '2']: # 只要结果不是交易失败 或者交易撤销 都算退款成功
  349. finishedTime = datetime.datetime.now()
  350. matched = self.refundOrder.succeed("", finishedTime = finishedTime)
  351. if matched:
  352. refund_post_pay(self.refundOrder, finishedTime)
  353. self.refundOrder.update(attachParas__refund_to_user = result)
  354. logger.debug('RCU Refund request successfully! return = {}'.format(result))
  355. else:
  356. raise UserServerException(u'已经有退款订单正在运行')
  357. else:
  358. logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo,
  359. result['TransStatus']))
  360. self.refundOrder.fail(errorCode = result["TransStatus"])
  361. raise UserServerException(result["TransStatus"])
  362. elif payGateway.pay_app_type == PayAppType.JD_OPEN:
  363. try:
  364. result = payGateway.refund_to_user(
  365. out_trade_no = self.outTradeNo,
  366. out_refund_no = self.refundOrder.orderNo,
  367. refund_fee = self.refundFee,
  368. total_fee = self.totalFee,
  369. refund_reason = u'退费',
  370. callbackUrl = REFUND_NOTIFY_URL.JDOPEN_REFUND_BACK,
  371. ledgerInfoList = payGateway.refund_bill_split_list(refund_order_record.partition_map))
  372. except JdOpenException as e:
  373. logger.info('refund failed , refund orderNo = {} reason = {}'.format(self.refundOrder.orderNo, e))
  374. self.refundOrder.fail(errorCode = e.errCode, errorDesc = e.errMsg)
  375. raise UserServerException('{}({})'.format(e.errMsg, e.errCode))
  376. logger.info('JDAGGRE Refund request successfully! return = {}'.format(result))
  377. else:
  378. self.refundOrder.fail(errorDesc = u"不支持的退款模式")
  379. raise UserServerException(u"不支持的退款模式")
  380. except UserServerException as se:
  381. logger.error(se.message)
  382. raise se
  383. except Exception as ee:
  384. # 这一步就不再更改订单的状态 由于不知道是退款前出错还是退款后出错 使用poll拉取订单状态来更新
  385. logger.exception(ee)
  386. raise UserServerException(ee.message)
  387. finally:
  388. # 资金池方式下,直接记录负单.所有对账时间都以系统内时间为准
  389. if payGateway.occupant.role == ROLE.agent:
  390. from apps.web.report.ledger import Ledger
  391. ledger = Ledger(USER_RECHARGE_TYPE.REFUND_CASH, refund_order_record)
  392. ledger.execute(journal = False, stats = True, check = False)
  393. return self.refundOrder
  394. def refund_post_pay(refundOrder, finishedTime):
  395. # type: (RefundMoneyRecord, datetime)->None
  396. refundOrder.user.commit_refund_cash(refundOrder)
  397. refund_order_record = refundOrder.refund_order_record # type: RechargeRecord
  398. refund_order_record.finishedTime = finishedTime
  399. refund_order_record.result = RechargeRecord.PayResult.SUCCESS
  400. refund_order_record.save()
  401. # 记录资金池的变动
  402. if not refund_order_record.is_ledgered:
  403. from apps.web.report.ledger import Ledger
  404. ledger = Ledger(DEALER_INCOME_SOURCE.REFUND_CASH, refund_order_record)
  405. ledger.execute(stats=True)
  406. def frozen_refund_for_balance(refundOrder, user = None, minus_total_consume = VirtualCoin(0)):
  407. # type: (RefundMoneyRecord, MyUser, VirtualCoin)->bool
  408. if user:
  409. if user.openId != refundOrder.pay_sub_order.openId:
  410. raise UserServerException(u"用户参数错误")
  411. else:
  412. if user.groupId != refundOrder.pay_sub_order.groupId:
  413. user = refundOrder.pay_sub_order.user
  414. else:
  415. user = refundOrder.pay_sub_order.user
  416. if not user:
  417. raise UserServerException(u'用户不存在')
  418. return user.prepare_refund_cash(refundOrder, minus_total_consume)