policy_common.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import json
  5. import logging
  6. import traceback
  7. from arrow import Arrow
  8. from django.conf import settings
  9. from typing import TYPE_CHECKING
  10. from apilib.monetary import RMB, VirtualCoin
  11. from apilib.utils_string import make_title_from_dict
  12. from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND, CONSUMETYPE
  13. from apps.web.eventer.base import ComNetPayAckEvent, AckEventProcessorIntf, IdStartAckEvent
  14. from apps.web.user.models import VCardConsumeRecord, ConsumeRecord, RechargeRecord, UserVirtualCard, RefundMoneyRecord
  15. from apps.web.user.transaction_deprecated import refund_cash
  16. if TYPE_CHECKING:
  17. from apps.web.device.models import DeviceDict
  18. logger = logging.getLogger(__name__)
  19. class StartAckEventPreProcessor(AckEventProcessorIntf):
  20. def analysis_reason(self, reason, fault_code=None):
  21. FINISHED_CHARGE_REASON_MAP = {
  22. # 服务器定义的停止事件
  23. 0xC0: u'计费方式无效',
  24. 0xC1: u'订购套餐已用完',
  25. 0xC2: u'订购时间已用完',
  26. 0xC3: u'订购电量已用完',
  27. 0xC4: u'动态计算功率后, 时间已用完',
  28. 0xC5: u'订单异常,设备可能离线超过1小时, 平台结单',
  29. 0xC6: u'系统检测到充电已结束, 平台结单(0x21)',
  30. 0xC7: u'系统检测到充电已结束, 平台结单(0x15)',
  31. 0xC8: u'用户远程停止订单',
  32. 0xC9: u'经销商远程停止订单',
  33. 0xCA: u'系统检测到订单已结束, 平台结单(0xCA)',
  34. 0xCB: u'充电时长已达到最大限制(0xCB)',
  35. 0xCC: u'充电电量已达到最大限制(0xCC)',
  36. 0xCD: u'设备升级中... 关闭当前订单',
  37. }
  38. return FINISHED_CHARGE_REASON_MAP.get(reason, '充电结束')
  39. def pre_processing(self, device, event_data):
  40. # type:(DeviceDict, dict)->dict
  41. source = json.dumps(event_data, indent=4)
  42. event_data['source'] = source
  43. if 'duration' in event_data:
  44. event_data['duration'] = round(event_data['duration'] / 60.0, 1)
  45. if 'elec' in event_data:
  46. event_data['elec'] = round(event_data['elec'] / 3600000.0, 3)
  47. if 'rule' in event_data:
  48. rule = event_data['rule']
  49. if 'need_time' in rule:
  50. event_data['needTime'] = round(rule['need_time'] / 60.0, 2)
  51. if 'need_elec' in rule:
  52. event_data['needElec'] = round(rule['need_elec'] / 3600000.0, 3)
  53. if 'amount' in rule:
  54. event_data['amount'] = round(rule['amount'] / 100.0, 2)
  55. if 'need_pay' in event_data:
  56. if event_data['need_pay'] == -1: # 由服务器计算, 模块计算由精度损失
  57. event_data['needPay'] = 0.0
  58. else:
  59. event_data['needPay'] = round(event_data['need_pay'] / 3600.0 / 100.0, 2)
  60. if 'status' in event_data and event_data['status'] == 'finished':
  61. event_data['reasonDesc'] = self.analysis_reason(event_data.get('reason'))
  62. if event_data.get('duration', 5) < 5 and event_data.get('reason') in [0xC1, 0xC2, 0xC3, 0xC4]:
  63. event_data['reasonDesc'] = '充电结束(可能为异常导致, 如有疑问, 请联系经销商.)'
  64. if 'fee' in event_data: # 仅仅是本笔订单的金额,续充单的扣费使用的
  65. event_data['fee'] = round(event_data['fee'] * 0.01, 2)
  66. if 'card_id' in event_data:
  67. event_data['cardNo'] = format(int(event_data['card_id'], 16), 'd')
  68. if 'sub' in event_data:
  69. for sub in event_data['sub']:
  70. if 'fee' in sub:
  71. sub['fee'] = round(sub['fee'] * 0.01, 2)
  72. return event_data
  73. class PolicyComNetPayAckEvent(ComNetPayAckEvent):
  74. def __init__(self, smartBox, event_data):
  75. super(PolicyComNetPayAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor())
  76. def post_before_start(self, order=None):
  77. # 记录处理的源数据报文
  78. uart_source = getattr(order, 'uart_source', [])
  79. uart_source.append({
  80. 'rece_running': self.event_data.get('source'),
  81. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  82. })
  83. order.uart_source = uart_source
  84. order.save()
  85. def post_after_start(self, order=None):
  86. pass
  87. def post_before_finish(self, order=None):
  88. # 记录处理的源数据报文
  89. uart_source = getattr(order, 'uart_source', [])
  90. uart_source.append({
  91. 'rece_finished': self.event_data.get('source'),
  92. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  93. })
  94. order.uart_source = uart_source
  95. order.save()
  96. def post_after_finish(self, order=None):
  97. pass
  98. def merge_order(self, master_order, sub_orders):
  99. # type:(ConsumeRecord, list)->dict
  100. start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
  101. port_info = {
  102. 'start_time': start_time.format(Const.DATETIME_FMT),
  103. 'estimatedTs': int(start_time.timestamp + 3600 * 12),
  104. }
  105. billing_method = self.event_data['billing_method']
  106. if billing_method == CONSUMETYPE.POSTPAID:
  107. if 'needTime' in self.event_data:
  108. port_info.update({
  109. 'needKind': 'needTime',
  110. 'needValue': self.event_data['needTime']
  111. })
  112. elif 'needElec' in self.event_data:
  113. port_info.update({
  114. 'needKind': 'needElec',
  115. 'needValue': self.event_data['needElec']
  116. })
  117. port_info.update({
  118. 'coins': str(master_order.coin),
  119. 'consumeType': CONSUMETYPE.POSTPAID,
  120. })
  121. else:
  122. coins = VirtualCoin(master_order.coin)
  123. for sub_order in sub_orders:
  124. coins += VirtualCoin(sub_order.coin)
  125. port_info.update({
  126. 'coins': coins.mongo_amount,
  127. 'consumeType': CONSUMETYPE.MOBILE,
  128. })
  129. # 免费地址组更新
  130. master_order.attachParas.get('isFree') and port_info.update({'consumeType': 'isFree'})
  131. return port_info
  132. def do_finished_event(self, master_order, sub_orders, merge_order_info):
  133. # type: (ConsumeRecord, [ConsumeRecord], dict)->None
  134. billing_method = self.event_data['billing_method']
  135. if billing_method == CONSUMETYPE.POSTPAID:
  136. self.do_postpaid_time_finished(master_order, sub_orders, merge_order_info)
  137. else:
  138. self.do_prepaid_time_finished(master_order, sub_orders, merge_order_info)
  139. def insert_vCard_consume_record(self, vCard, order, success, consumeTotal, consumeDay):
  140. try:
  141. if success and consumeDay['count'] > 0:
  142. record = VCardConsumeRecord(
  143. orderNo=VCardConsumeRecord.make_no(order.logicalCode),
  144. openId=order.openId,
  145. nickname=order.nickname,
  146. cardId=str(vCard.id),
  147. dealerId=vCard.dealerId,
  148. devNo=order.devNo,
  149. devTypeCode = self.device.devTypeCode,
  150. devTypeName = self.device.devTypeName,
  151. logicalCode=order.logicalCode,
  152. groupId=order.groupId,
  153. address=order.address,
  154. groupNumber=order.groupNumber,
  155. groupName=order.groupName,
  156. attachParas=order.attachParas,
  157. consumeData=consumeTotal,
  158. consumeDayData=consumeDay
  159. )
  160. record.save()
  161. except Exception as e:
  162. logger.exception(e)
  163. def do_postpaid_time_finished(self, master_order, sub_orders=None, merge_order_info=None):
  164. duration = self.event_data.get('duration', 0)
  165. elec = self.event_data.get('elec', 0)
  166. needPay = VirtualCoin(self.event_data.get('needPay', 0))
  167. # 套餐最小使用金额
  168. minFee = VirtualCoin(master_order.package.get('minFee') or 0)
  169. needPay = max(needPay, minFee)
  170. # 保护时间判断
  171. refundProtectionTime = int(self.device.otherConf.get('serverConfigs', {}).get('refundProtectionTime', 5))
  172. # 免费地址判断
  173. if duration < refundProtectionTime or master_order.is_free:
  174. needPay = VirtualCoin(0)
  175. consumeDict = master_order.servicedInfo
  176. consumeDict.update({
  177. 'reason': self.event_data.get('reasonDesc'),
  178. 'chargeIndex': str(master_order.used_port),
  179. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  180. DEALER_CONSUMPTION_AGG_KIND.ELEC: elec,
  181. })
  182. logger.debug(
  183. 'orderNo = {}, orderType = isPostpaid, isFree={} usedTime = {}, needPayMoney = {}'.format(
  184. master_order.orderNo, master_order.is_free, duration, needPay))
  185. # 1 获取金币汇率
  186. coinRatio = float(self.device.otherConf.get('serverConfigs', {}).get('coinRatio', 1))
  187. # 2 更新订单金额
  188. master_order.coin = (needPay * coinRatio).mongo_amount
  189. master_order.money = needPay
  190. master_order.servicedInfo = consumeDict
  191. master_order.save()
  192. # 3 使用金币支付
  193. self.pay_order(master_order)
  194. master_order.reload()
  195. extra = [{u'使用详情': '{}号端口, {}分钟'.format(master_order.used_port, duration)}]
  196. if master_order.is_free:
  197. extra.append({u'消费详情': u'免费使用'})
  198. else:
  199. if master_order.status == ConsumeRecord.Status.FINISHED:
  200. if master_order.paymentInfo.get('via') == 'virtualCard':
  201. desc = u'(已使用优惠卡券抵扣本次消费)'
  202. master_order.update(coin=VirtualCoin(0), money=RMB(0))
  203. # 结算完了进行退虚拟卡额度处理:
  204. try:
  205. if "rcdId" in master_order.paymentInfo:
  206. consumeRcdId = master_order.paymentInfo['rcdId']
  207. else:
  208. consumeRcdId = master_order.paymentInfo['itemId']
  209. vCardConsumeRcd = VCardConsumeRecord.objects.get(id=consumeRcdId)
  210. vCard = UserVirtualCard.objects.get(id=vCardConsumeRcd.cardId)
  211. vCard.refund_quota(vCardConsumeRcd, duration, 0.0, VirtualCoin(0).mongo_amount)
  212. except:
  213. pass
  214. else:
  215. desc = u'(已使用账户余额自动结算本次消费)' if needPay > VirtualCoin(0) else ''
  216. else:
  217. desc = u'(您的账户余额已不足以抵扣本次消费,请前往账单中心进行支付)'
  218. self.event_data['reasonDesc'] += desc
  219. extra.append({u'消费金额': u'{}元'.format(needPay * coinRatio)})
  220. extra.append({u'用户余额': u'{}元'.format(master_order.user.calc_currency_balance(self.device.owner, self.device.group))})
  221. self.notify_to_user(master_order.user.managerialOpenId, extra, url=self.device.deviceAdapter.custom_push_url(master_order, master_order.user))
  222. def notify_to_user(self, openId, extra, url=None):
  223. group = self.device.group
  224. self.notify_user_service_complete(
  225. service_name='充电',
  226. openid=openId,
  227. port='',
  228. address=group['address'],
  229. reason=self.event_data.get('reasonDesc'),
  230. finished_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  231. extra=extra,
  232. url=url
  233. )
  234. def pay_order(self, order):
  235. order.status = 'running'
  236. order.attachParas['packageId'] = order.package.get('packageId')
  237. order.save()
  238. order.s_to_e()
  239. def do_prepaid_time_finished(self, master_order, sub_orders, merge_order_info):
  240. duration, elec = self.event_data.get('duration', 0), self.event_data.get('elec', 0)
  241. usedFee = VirtualCoin(self.event_data['needPay'])
  242. coins = VirtualCoin(merge_order_info['coins'])
  243. # 保护时间判断
  244. refundProtectionTime = int(self.device.otherConf.get('serverConfigs', {}).get('refundProtectionTime', 5))
  245. if duration < refundProtectionTime:
  246. usedFee = VirtualCoin(0)
  247. backCoins = coins - usedFee
  248. logger.debug(
  249. 'orderNo = {}, orderType = isPostpaid, usedTime = {},coins = {}, usedFee = {}, backCoins = {}'.format(
  250. master_order.orderNo,
  251. duration, coins,
  252. usedFee, backCoins))
  253. if master_order.is_temp_package:
  254. extra = [{u'使用详情': '{}号端口, {}分钟(临时套餐)'.format(master_order.used_port, duration)}]
  255. else:
  256. extra = [{u'使用详情': '{}号端口, {}分钟'.format(master_order.used_port, duration)}]
  257. if master_order.paymentInfo['via'] == 'free':
  258. extra.append({u'消费详情': u'免费使用'})
  259. elif master_order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
  260. all_money = RMB(0)
  261. all_refund_money = RMB(0)
  262. orders = [master_order] + sub_orders
  263. for _order in orders[::-1]:
  264. consumeDict = {
  265. 'reason': self.event_data.get('reasonDesc'),
  266. 'chargeIndex': str(master_order.used_port),
  267. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  268. DEALER_CONSUMPTION_AGG_KIND.ELEC: elec,
  269. }
  270. all_money += RMB(_order.money)
  271. need_back_coins, need_consume_coins, backCoins = self._calc_refund_info(backCoins, _order.coin)
  272. isTempPackage = 'isTempPackage' in _order.attachParas
  273. is_cash = (isTempPackage and _order.package.get('autoRefund', False)) or (
  274. duration < refundProtectionTime)
  275. # 临时套餐 + 套餐内退费开关已打开
  276. if is_cash:
  277. self.clear_frozen_user_balance(_order, need_back_coins, consumeDict, True)
  278. all_refund_money += RMB(consumeDict[DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH])
  279. else:
  280. self.clear_frozen_user_balance(_order, VirtualCoin(0), consumeDict, True)
  281. _order.update_service_info(consumeDict)
  282. if all_refund_money > RMB(0):
  283. extra.append({u'消费详情': '支付{}元, 退款{}元'.format(all_money, all_refund_money)})
  284. else:
  285. extra.append({u'消费详情': '支付{}元'.format(all_money)})
  286. else:
  287. logger.error('not net pay rather user virtual card pay. something is wrong.')
  288. return
  289. extra.append({u'用户余额': u'{}元'.format(master_order.user.calc_currency_balance(self.device.owner, self.device.group))})
  290. self.notify_to_user(master_order.user.managerialOpenId, extra)
  291. def _calc_refund_info(self, backCoins, orderCoin):
  292. if backCoins >= orderCoin:
  293. need_back_coins = orderCoin
  294. need_consume_coins = VirtualCoin(0)
  295. backCoins -= orderCoin
  296. else:
  297. need_back_coins = backCoins
  298. need_consume_coins = orderCoin - need_back_coins
  299. backCoins = VirtualCoin(0)
  300. return need_back_coins, need_consume_coins, backCoins
  301. def clear_frozen_user_balance(self, order, backCoins, consumeDict, is_cash):
  302. # type:(ConsumeRecord, VirtualCoin, dict, bool) -> None
  303. paymentInfo = order.paymentInfo
  304. if not paymentInfo or paymentInfo['via'] not in ['netPay', 'coins', 'cash', 'coin']:
  305. raise Exception('method is not this process..')
  306. payCoins = VirtualCoin(paymentInfo['coins'])
  307. user = order.user
  308. if not user:
  309. logger.error(
  310. 'refund coins<{}> failure(no found user). consumeRecord = {}'.format(backCoins, repr(order)))
  311. return
  312. # 金币做一个保护
  313. backCoins = min(payCoins, backCoins)
  314. if backCoins <= VirtualCoin(0):
  315. if is_cash:
  316. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH: RMB(0).mongo_amount})
  317. else:
  318. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: RMB(0).mongo_amount})
  319. logger.error('refund coins<{}>. consumeRecord = {}'.format(backCoins, repr(order)))
  320. return
  321. itemId = paymentInfo.get('itemId')
  322. if itemId:
  323. rechargeRcd = RechargeRecord.objects.filter(id=itemId, isQuickPay=True, result='success',
  324. devNo=order.devNo).first()
  325. else:
  326. rechargeRcd = None
  327. if is_cash and rechargeRcd:
  328. payRMB = RMB(rechargeRcd.money)
  329. refundRMB = payRMB * (float(backCoins) / float(payCoins))
  330. # 冻结金额全部扣除
  331. user.clear_frozen_balance(str(order.id), paymentInfo['deduct'],
  332. back_coins=VirtualCoin(0),
  333. consume_coins=VirtualCoin(payCoins))
  334. try:
  335. refund_order = refund_cash(
  336. rechargeRcd, refundRMB, VirtualCoin(0), user = user) # type: RefundMoneyRecord
  337. if not refund_order:
  338. logger.error(
  339. 'refund cash<{}> failure. recharge = {}'.format(refundRMB, repr(rechargeRcd)))
  340. except Exception:
  341. logger.exception(traceback.format_exc())
  342. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH: refundRMB.mongo_amount})
  343. else:
  344. user.clear_frozen_balance(str(order.id), paymentInfo['deduct'], back_coins=backCoins,
  345. consume_coins=(payCoins - backCoins))
  346. consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: backCoins.mongo_amount})
  347. class PolicyOnlineCardStartAckEvent(IdStartAckEvent):
  348. def __init__(self, smartBox, event_data):
  349. super(PolicyOnlineCardStartAckEvent, self).__init__(smartBox, event_data, StartAckEventPreProcessor())
  350. def post_before_start(self, order=None):
  351. # 记录处理的源数据报文
  352. uart_source = getattr(order, 'uart_source', [])
  353. uart_source.append({
  354. 'rece_running': self.event_data.get('source'),
  355. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  356. })
  357. order.uart_source = uart_source
  358. order.save()
  359. def post_after_start(self, order=None):
  360. self.card.reload()
  361. # 通知用户,已经扣费
  362. title = make_title_from_dict([
  363. {u'设备地址': u'{}'.format(self.device.group.address)},
  364. {u'设备编号': u'{}-{}'.format(self.device['logicalCode'], order.used_port)},
  365. {u'实体卡': u'{}--No:{}'.format(self.card.cardName or self.card.nickName, self.card.cardNo)},
  366. {u'本次消费': u'{} 元'.format(order.coin)},
  367. {u'卡余额': u'{} 元'.format(self.card.balance)},
  368. ])
  369. start_time_stamp = self.event_data.get('sts')
  370. start_time = datetime.datetime.fromtimestamp(start_time_stamp)
  371. self.notify_user(
  372. self.card.managerialOpenId,
  373. 'dev_start',
  374. **{
  375. 'title': title,
  376. 'things': u'刷卡消费',
  377. 'remark': u'感谢您的支持!',
  378. 'time': start_time.strftime(Const.DATETIME_FMT),
  379. 'url': self.deviceAdapter.custom_push_url(order, order.user)
  380. }
  381. )
  382. def post_before_finish(self, order=None):
  383. # 记录处理的源数据报文
  384. uart_source = getattr(order, 'uart_source', [])
  385. uart_source.append({
  386. 'rece_finished': self.event_data.get('source'),
  387. 'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  388. })
  389. order.uart_source = uart_source
  390. order.save()
  391. def post_after_finish(self, order=None):
  392. pass
  393. def merge_order(self, master_order, sub_orders):
  394. # type:(ConsumeRecord, list)->dict
  395. billing_method = self.event_data.get('billing_method')
  396. start_time = Arrow.fromdatetime(master_order.startTime, tzinfo=settings.TIME_ZONE)
  397. if billing_method == CONSUMETYPE.POSTPAID:
  398. portDict = {
  399. 'coins': '0.0',
  400. 'money': '0.0',
  401. 'start_time': start_time.format(Const.DATETIME_FMT),
  402. 'estimatedTs': int(start_time.timestamp + 3600 * 12),
  403. 'consumeType': CONSUMETYPE.POSTPAID
  404. }
  405. else:
  406. portDict = {
  407. 'coins': str(VirtualCoin(self.event_data.get('amount', 0))),
  408. 'money': str(VirtualCoin(self.event_data.get('amount', 0))),
  409. 'start_time': start_time.format(Const.DATETIME_FMT),
  410. 'estimatedTs': int(start_time.timestamp + 3600 * 12),
  411. 'consumeType': CONSUMETYPE.CARD
  412. }
  413. return portDict
  414. def _do_prepaid_finish(self, order, merge_order_info):
  415. # type: (ConsumeRecord, dict)->None
  416. duration, elec = self.event_data.get('duration', 0.0), self.event_data.get('elec', 0.0),
  417. consumeDict = {
  418. 'reason': self.event_data.get('reasonDesc', None),
  419. }
  420. # 默认状态不退费
  421. coins = VirtualCoin(self.event_data['amount'])
  422. useFee = VirtualCoin(self.event_data['amount'])
  423. backCoins = VirtualCoin(0)
  424. # 保护时间判断
  425. refundProtectionTime = int(self.device.otherConf.get('serverConfigs', {}).get('refundProtectionTime', 5))
  426. auto_refund = self.device.policyTemp.get('forIdcard', {}).get('autoRefund', False)
  427. if duration < refundProtectionTime:
  428. useFee = VirtualCoin(0)
  429. backCoins = coins - useFee
  430. else:
  431. if auto_refund:
  432. useFee = VirtualCoin(self.event_data['needPay'])
  433. backCoins = coins - useFee
  434. logger.debug('{} auto refund enable switch is {}, coins={}, backCoins={}'.format(
  435. repr(self.device), auto_refund, coins, backCoins))
  436. # 分批塞入订单信息
  437. master_info = {
  438. 'order_id': self.event_data['order_id'],
  439. 'fee': order.coin,
  440. }
  441. order_processing_list = [master_info]
  442. if 'sub' in self.event_data:
  443. order_processing_list += self.event_data['sub']
  444. # 订单服务信息与退款处理
  445. for _info in order_processing_list[::-1]:
  446. _order = ConsumeRecord.objects.filter(devNo=self.device.devNo, startKey=_info['order_id']).first()
  447. if not _order:
  448. continue
  449. consumeDict.update({
  450. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: _order.coin,
  451. })
  452. # 全退
  453. if backCoins >= VirtualCoin(_order.coin):
  454. consumeDict.update({
  455. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: _order.coin.mongo_amount,
  456. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: _order.coin.mongo_amount,
  457. })
  458. self.card.clear_frozen_balance(str(_order.id), _order.coin)
  459. self.record_refund_money_for_card(_order.coin, str(self.card.id), orderNo=order.orderNo)
  460. backCoins -= _order.coin
  461. else: # 部分退
  462. consumeDict.update({
  463. DEALER_CONSUMPTION_AGG_KIND.CONSUME_CARD: _order.coin.mongo_amount,
  464. DEALER_CONSUMPTION_AGG_KIND.REFUND_CARD: backCoins.mongo_amount,
  465. })
  466. self.card.clear_frozen_balance(str(_order.id), backCoins)
  467. self.record_refund_money_for_card(backCoins, str(self.card.id), orderNo=order.orderNo)
  468. backCoins = VirtualCoin(0)
  469. _order.update_service_info(consumeDict)
  470. consumeDict.update({
  471. DEALER_CONSUMPTION_AGG_KIND.DURATION: duration,
  472. DEALER_CONSUMPTION_AGG_KIND.ELEC: elec,
  473. # DEALER_CONSUMPTION_AGG_KIND.ELECFEE: self.deviceAdapter.calc_elec_fee(elec),
  474. })
  475. order.update_service_info(consumeDict)
  476. self.card.reload()
  477. extra = [
  478. {u'在线卡片': '{}--No:{}'.format(self.card.cardName, self.card.cardNo)},
  479. {u'使用详情': '{}号端口, {}分钟'.format(order.used_port, duration)}
  480. ]
  481. if (coins - useFee) > VirtualCoin(0):
  482. extra.append({u'消费详情': '支付{}元,退费{}元'.format(coins, coins - useFee)})
  483. else:
  484. extra.append({u'消费详情': '支付{}元'.format(coins)})
  485. extra.append({u'卡片余额': '{}元'.format(self.card.balance.mongo_amount)})
  486. self.notify_user_service_complete(
  487. service_name='充电',
  488. openid=self.card.managerialOpenId,
  489. port='',
  490. address=order.address,
  491. reason=self.event_data.get('reasonDesc'),
  492. finished_time=order.finishedTime.strftime('%Y-%m-%d %H:%M:%S'),
  493. extra=extra)
  494. # 更新一次缓存
  495. self.deviceAdapter.async_update_portinfo_from_dev()
  496. def do_finished_event(self, order, merge_order_info):
  497. # type:(ConsumeRecord, dict)->None
  498. self._do_prepaid_finish(order, merge_order_info)
  499. def checkout_order(self, order):
  500. # 在线卡 执行扣费
  501. fee = VirtualCoin(order.coin)
  502. self.card.freeze_balance(transaction_id=str(order.id), fee=fee)
  503. def _calc_refund_info(self, backCoins, orderCoin):
  504. if backCoins >= orderCoin:
  505. need_back_coins = orderCoin
  506. need_consume_coins = VirtualCoin(0)
  507. backCoins -= orderCoin
  508. else:
  509. need_back_coins = backCoins
  510. need_consume_coins = orderCoin - need_back_coins
  511. backCoins = VirtualCoin(0)
  512. return need_back_coins, need_consume_coins, backCoins