weifule_policy.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import logging
  5. import typing
  6. from typing import TYPE_CHECKING
  7. from apilib.monetary import RMB
  8. from apilib.utils_sys import memcache_lock
  9. from apps.web.constant import ErrorCode, START_DEVICE_STATUS, DEALER_CONSUMPTION_AGG_KIND
  10. from apps.web.device.models import Group, Device
  11. from apps.web.eventer import EventBuilder
  12. from apps.web.eventer.weifuleCommon import WeiFuLeStatusEvent, WeiFuLePolicyProcess
  13. from apps.web.exceptions import UserServerException
  14. from apps.web.user.constant2 import ConsumeOrderServiceItem
  15. from apps.web.user.models import ServiceProgress, Card, CardRechargeOrder, CardRechargeRecord, UserVirtualCard
  16. from apps.web.user.utils import get_consume_order
  17. from apps.web.user.utils2 import ConsumeOrderStateEngine, generate_net_payment, generate_card_payment, generate_refund
  18. from apps.web.utils import set_start_key_status
  19. if typing.TYPE_CHECKING:
  20. from apps.web.user.models import ConsumeRecord
  21. logger = logging.getLogger(__name__)
  22. created_order_32 = 32
  23. exec_order_33 = 33
  24. finished_order_34 = 34
  25. id_card_request_35 = 35
  26. card_recharge_order_36 = 36
  27. status_change_event_44 = 44
  28. ic_consume_event_48 = 48 # ic卡花钱的时候,会上报此事件
  29. part_sn_event = 49 # 组件上报相关信息
  30. card_is_normal = 1
  31. card_not_in_db = 2
  32. card_is_forzen = 3
  33. card_has_not_order = 4 # IC卡适用
  34. card_less_balance = 5 # ID卡适用
  35. card_type_is_ic = 6 # IC卡不允许当做ID卡使用
  36. no_load_code = 7
  37. if TYPE_CHECKING:
  38. from apps.web.core.adapter.base import SmartBox
  39. from apps.web.core.adapter.weifule_policy import POLICYBox
  40. class builder(EventBuilder):
  41. def __getEvent__(self, device_event):
  42. event_data = self.deviceAdapter.analyze_event_data(device_event['data'])
  43. if event_data is None:
  44. return None
  45. if event_data['fun_code'] in [
  46. created_order_32,
  47. exec_order_33,
  48. finished_order_34,
  49. id_card_request_35,
  50. card_recharge_order_36
  51. ]:
  52. return ChargingSocketWorkEvent(self.deviceAdapter, event_data)
  53. if event_data['fun_code'] in [status_change_event_44]:
  54. return WeiFuLeStatusEvent(self.deviceAdapter, device_event)
  55. class ChargingSocketWorkEvent(WeiFuLePolicyProcess):
  56. def __init__(self, smartBox, event_data): # type:(SmartBox,dict)->None
  57. super(ChargingSocketWorkEvent, self).__init__(smartBox, event_data)
  58. self.deviceAdapter = smartBox # type: POLICYBox
  59. def create_progress_for_socket_order(self, consumeRcd, devInfo):
  60. try:
  61. port = int(devInfo['port'])
  62. progress = ServiceProgress.objects.filter(device_imei=self.device.devNo, port=port,
  63. devTypeCode=self.device.devTypeCode).first()
  64. if not progress:
  65. progress = ServiceProgress(device_imei=self.device.devNo, port=port,
  66. devTypeCode=self.device.devTypeCode)
  67. progress.start_time = devInfo['create_time']
  68. progress.finished_time = devInfo['create_time'] + 60 * 60 * 12
  69. progress.isFinished = False
  70. else:
  71. if devInfo['id'] in progress.consumes:
  72. return
  73. if progress.isFinished is False:
  74. progress.finished_time += 60 * 60 * 12
  75. else:
  76. progress.consumes = []
  77. progress.start_time = devInfo['create_time']
  78. progress.finished_time = devInfo['create_time'] + devInfo.get('amount_time', 60 * 60 * 12)
  79. progress.isFinished = False
  80. progress.open_id = consumeRcd.openId
  81. progress.consumes.append(devInfo['id'])
  82. progress.status = 'running'
  83. progress.save()
  84. except Exception as e:
  85. logger.exception(e)
  86. def do(self, **args):
  87. devNo = self.device['devNo']
  88. funCode = self.event_data.get('fun_code')
  89. order = self.event_data.get('order', {})
  90. logger.info('weifule charging event detected, devNo={}, event = {}'.format(devNo, self.event_data))
  91. # 刷卡查询余额的上报
  92. if funCode == id_card_request_35:
  93. return self.response_id_card()
  94. with memcache_lock(key='%s-%s-%s-finished' % (devNo, order.get('id'), funCode), value='1', expire=120) as acquired:
  95. if not acquired:
  96. logger.info('weifule charging event is doing !!!, devNo={}'.format(devNo))
  97. return
  98. self._do_ack_order(order)
  99. try:
  100. if funCode == created_order_32:
  101. self._do_created_order_32(order)
  102. elif funCode == exec_order_33:
  103. self._do_exec_order_33(order)
  104. elif funCode == finished_order_34:
  105. if order["order_type"] == 'card_charge':
  106. self.update_card_recharge_for_success_event(order['id'], RMB(order['balance'] / 100.0))
  107. elif order["order_type"] == 'card_refund':
  108. self.end_for_card_refund(order)
  109. else:
  110. self._do_finished_order_34(order)
  111. elif funCode == card_recharge_order_36: # 如果是卡充值,直接回复订单
  112. self.deal_with_ic_charge_event()
  113. except Exception as e:
  114. logger.exception(e)
  115. logger.info('deal order is finished < funCode: {} order: {} >'.format(funCode, order.get('id')))
  116. def translate_reason(self, order):
  117. cause = order.get('cause')
  118. if cause == 1:
  119. if self.duration < self.refundProtectionTime:
  120. return u'充电已结束,如有异常情况,请联系本台设备经销商。'
  121. else:
  122. return u'订购的套餐已用完。'
  123. elif cause == 2:
  124. return u'用户远程停止。'
  125. elif cause == 3:
  126. return u'管理员操作停止。'
  127. elif cause == 4:
  128. return u'经销商远程停止。'
  129. elif cause == 5:
  130. return u'用户拔掉充电器,或者插座脱落。'
  131. elif cause == 6:
  132. return u'端口功率过载,系统为了安全,关闭此充电端口。'
  133. elif cause == 7:
  134. return u'整机电压过载,系统为了安全,关闭所有充电端口。'
  135. elif cause == 8:
  136. return u'端口电流过载,系统为了安全,关闭此充电端口。'
  137. elif cause == 9:
  138. return u'整机功率超限,系统为了安全,关闭所有充电端口。'
  139. elif cause == 10:
  140. return u'检测到温度超限,系统为了安全,关闭所有充电端口。'
  141. elif cause == 11:
  142. return u'恭喜您电池已经充满'
  143. elif cause == 12:
  144. if self.duration < self.refundProtectionTime:
  145. return u'充电已结束,如有异常情况,请联系设备经销商。'
  146. else:
  147. return u'订购的时间已用完'
  148. elif cause == 13:
  149. if self.duration < self.refundProtectionTime:
  150. return u'充电已结束,如有异常情况,请联系本台设备经销商。'
  151. else:
  152. return u'订购的电量已用完'
  153. elif cause == 0x0E:
  154. return u"当前充电功率超过套餐允许最大功率"
  155. elif cause == 20:
  156. return u'端口功率过小。可能是电池已经充满,也可能是所接负载功率太小'
  157. return u'充电结束。'
  158. def deal_with_ic_charge_event(self):
  159. cardNo = self.event_data['card_no']
  160. card = self.update_card_dealer_and_type(cardNo, 'IC')
  161. if not card:
  162. return self.deviceAdapter.response_card_charge_result(self.event_data['card_no'], card_not_in_db)
  163. elif card.frozen:
  164. return self.deviceAdapter.response_card_charge_result(self.event_data['card_no'], card_is_forzen)
  165. preBalance = card.balance
  166. rechargeOrder = CardRechargeOrder.get_last_to_do_one(str(card.id))
  167. if rechargeOrder:
  168. self.recharge_ic_card(card=card,
  169. preBalance=preBalance,
  170. rechargeType='append',
  171. order=rechargeOrder,
  172. need_verify=False)
  173. else:
  174. return self.deviceAdapter.response_card_charge_result(self.event_data['card_no'], card_has_not_order)
  175. def recharge_ic_card(self, card, preBalance, rechargeType, order, need_verify=True):
  176. # type:(Card, RMB, str, CardRechargeOrder, bool)->bool
  177. """
  178. # rechargeType有两种,一种是用直接覆写overwrite的方式,一种是append追加钱的方式。
  179. # 不同的的设备,充值的方式还不一样.注意:money是实际用户付的钱,coins是给用户充值的钱,比如付10块(money),充15(coins)。
  180. :param card:
  181. :param preBalance:
  182. :param rechargeType:
  183. :param order:
  184. :return:
  185. """
  186. if not order or order.coins == RMB(0):
  187. return False
  188. status = Card.get_card_status(str(card.id))
  189. if status == 'busy':
  190. return False
  191. Card.set_card_status(str(card.id), 'busy')
  192. try:
  193. # IC卡需要下发到设备,设备写卡,把余额打入卡中
  194. if rechargeType == 'overwrite':
  195. sendMoney = preBalance + order.coins
  196. else:
  197. sendMoney = order.coins
  198. # 先判断order最近一次充值是否OK. 满足三个条件才认为上次充值成功:
  199. # 1、操作时间已经超过三天
  200. # 2、操作结果是串口超时, 即result == 1
  201. # 3、当前余额大于最后一次充值操作的充值前余额
  202. if need_verify and len(order.operationLog) > 0:
  203. log = order.operationLog[-1]
  204. result = log['result']
  205. time_delta = (datetime.datetime.now() - log['time']).total_seconds()
  206. last_pre_balance = RMB(log['preBalance'])
  207. if (result == ErrorCode.DEVICE_CONN_FAIL or result == ErrorCode.BOARD_UART_TIMEOUT) \
  208. and (time_delta > 3 * 24 * 3600 or preBalance > last_pre_balance):
  209. logger.debug('{} recharge verify result is finished.'.format(repr(card)))
  210. order.update_after_recharge_ic_card(device=self.device,
  211. sendMoney=sendMoney,
  212. preBalance=preBalance,
  213. result=ErrorCode.SUCCESS,
  214. description=u'充值校验结束')
  215. CardRechargeRecord.add_record(
  216. card=card,
  217. group=Group.get_group(order.groupId),
  218. order=order,
  219. device=self.device)
  220. return False
  221. try:
  222. operation_result, balance = self.deviceAdapter.recharge_card(card.cardNo, sendMoney,
  223. orderNo=str(order.id))
  224. order.update_after_recharge_ic_card(device=self.device,
  225. sendMoney=sendMoney,
  226. preBalance=preBalance,
  227. syncBalance=balance,
  228. result=operation_result['result'],
  229. description=operation_result['description'])
  230. if operation_result['result'] != ErrorCode.SUCCESS:
  231. return False
  232. if not balance:
  233. balance = preBalance + order.coins
  234. CardRechargeRecord.add_record(
  235. card=card,
  236. group=Group.get_group(order.groupId),
  237. order=order,
  238. device=self.device)
  239. # 刷新卡里面的余额
  240. card.balance = balance
  241. card.lastMaxBalance = balance
  242. card.save()
  243. return True
  244. except Exception as e:
  245. order.update_after_recharge_ic_card(device=self.device,
  246. sendMoney=sendMoney,
  247. preBalance=preBalance,
  248. syncBalance=balance,
  249. result=ErrorCode.EXCEPTION,
  250. description=e.message)
  251. return False
  252. except Exception as e:
  253. logger.exception(e)
  254. return False
  255. finally:
  256. Card.set_card_status(str(card.id), 'idle')
  257. def prepaid_end_for_app_start(self, order):
  258. """
  259. :param order: 主板上报的 object order 信息
  260. """
  261. # 虚拟卡支付的
  262. if self.consumeRcd and self.consumeRcd.virtual_card_id:
  263. ue = order["elec"] / 1000 * 1000
  264. ut = order["time"] / 60.0
  265. vcardRcd = UserVirtualCard.objects.get(id=self.consumeRcd.virtual_card_id) # type: UserVirtualCard
  266. modified, consumeTotal, consumeDay = vcardRcd.clear_frozen_quota(str(self.consumeRcd.id), ut, ue, 0)
  267. # 支付完成之后 穿件一笔消费记录
  268. if not modified:
  269. return
  270. vcardRcd.new_consume_record(self.device, self.consumeRcd.user, consumeTotal)
  271. else:
  272. super(ChargingSocketWorkEvent, self).prepaid_end_for_app_start(order)
  273. # ---------------------------------------- 继承重写 防止误判之前的逻辑 --------------------------------------------
  274. def pay_apps_start_by_32(self, order, callbackSP=None):
  275. """
  276. 此函数会对订单进行扣款(补扣或者抢锁扣款,依据上报的事件)
  277. 一般callbackSP为创建服务进程函数
  278. """
  279. consumeRcd = get_consume_order(orderNo=order["id"]) # type: ConsumeRecord
  280. # 检查订单是否完成
  281. if consumeRcd is None:
  282. logger.info('[pay_apps_start_by_32] weifule do 32, not find order = {}, devNo = {}'.format(order['id'], self.device.devNo))
  283. return
  284. # 检测订单是否是合法状态
  285. if consumeRcd.status not in [
  286. consumeRcd.Status.WAIT_CONF,
  287. consumeRcd.Status.UNKNOWN,
  288. consumeRcd.Status.TIMEOUT
  289. ]:
  290. logger.info('[pay_apps_start_by_32] weifule do 32, order <{}> status = '.format(consumeRcd.orderNo, consumeRcd.orderNo))
  291. return
  292. # 走到此处 一般是订单状态还在异常状态 此时直接执行
  293. createTime = order["create_time"]
  294. deviceStartTime = datetime.datetime.fromtimestamp(createTime).strftime("%Y-%m-%d %H:%M:%S")
  295. ConsumeOrderStateEngine(consumeRcd).to_running({"deviceStartTime": deviceStartTime})
  296. set_start_key_status(start_key=consumeRcd.orderNo, state=START_DEVICE_STATUS.FINISHED, order_id=str(consumeRcd.id))
  297. consumeRcd.frozen_payer_balance()
  298. def _do_finished_order_34(self, order):
  299. """
  300. 处理订单的结束消息
  301. """
  302. # 查找订单
  303. consumeRcd = get_consume_order(orderNo=order['id']) # type: ConsumeRecord
  304. if not consumeRcd:
  305. logger.info('[_do_finished_order_34], not find order = {}, devNo = {}'.format(order['id'], self.device.devNo))
  306. return
  307. # 检查订单的状态
  308. if consumeRcd.status not in [consumeRcd.Status.RUNNING]:
  309. logger.info('[_do_finished_order_34], order <{}> status not normal'.format(order['id']))
  310. return
  311. # 组建订单的消费信息
  312. duration = round(order['time'] / 60.0, 1)
  313. elec = round(order['elec'] / 1000000.0, 3)
  314. power = round(order.get('power', 0), 1)
  315. reason = self._get_reason(order.get("cause"), duration, consumeRcd)
  316. consumeDict = {
  317. ConsumeOrderServiceItem.ELEC: elec,
  318. ConsumeOrderServiceItem.DURATION: duration,
  319. ConsumeOrderServiceItem.MAX_POWER: power,
  320. ConsumeOrderServiceItem.REASON: reason,
  321. ConsumeOrderServiceItem.END_TIME: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  322. ConsumeOrderServiceItem.SPEND: RMB(self.event_data["order"]['money'] * 0.01)
  323. }
  324. # 将订单的状态置为 结束 并记录服务信息
  325. ConsumeOrderStateEngine(consumeRcd).to_end(consumeDict)
  326. Device.clear_port_control_cache(self.device.devNo, str(order["port"]))
  327. # 以下步骤 实际上可以从event里面剥离开 直接进入订单处理中心排队完成 时间关系 暂时先放在event里面
  328. consumeRcd.reload()
  329. # 后支付的情况 需要将订单的金额算出来 并且生成支付信息 然后进行相应的支付
  330. if consumeRcd.isPostPaid:
  331. # 为后付费的订单添加订单的金额
  332. if consumeRcd.isFree:
  333. consumeRcd.price = RMB(0)
  334. else:
  335. if consumeRcd.service.duration <= consumeRcd.package.refundProtectTime:
  336. consumeRcd.price = RMB(0)
  337. else:
  338. # 最小消费金额 和 实际花费金额中选一个
  339. consumeRcd.price = max(consumeRcd.service.spendMoney, consumeRcd.package.minFee)
  340. consumeRcd.save()
  341. # 根据启动方式的不同 为后付费的订单生成相应的支付条件
  342. try:
  343. if consumeRcd.isStartNetPay:
  344. # 网络支付的生成
  345. payment = generate_net_payment(consumeRcd)
  346. elif consumeRcd.isStartCardPay:
  347. # 实体卡 支付的支付
  348. payment = generate_card_payment(consumeRcd)
  349. else:
  350. logger.error("[_do_finished_order_34], postPaid order <{}> error start type".format(order['id']))
  351. return
  352. except UserServerException:
  353. payment = None
  354. # 尝试一次支付
  355. if payment:
  356. consumeRcd.update_payment(payment)
  357. consumeRcd.frozen_payer_balance()
  358. consumeRcd.clear_payer_frozen()
  359. else:
  360. # 预支付 保护时间内 全额退费
  361. if consumeRcd.service.duration <= consumeRcd.package.refundProtectTime:
  362. refundMoney = consumeRcd.price
  363. else:
  364. if consumeRcd.package.autoRefund:
  365. refundMoney = consumeRcd.price - consumeRcd.service.spendMoney
  366. else:
  367. refundMoney = RMB(0)
  368. consumeRcd.clear_payer_frozen(refundMoney)
  369. # 尝试变更订单的状态 同时
  370. consumeRcd.reload()
  371. ConsumeOrderStateEngine(consumeRcd).to_finished()
  372. def _do_finished_order(self):
  373. pass
  374. @staticmethod
  375. def _get_reason(cause, duration, order): # type: (int, float, ConsumeRecord) -> basestring
  376. if cause == 1:
  377. if duration < order.package.refundProtectTime:
  378. return u'充电已结束,如有异常情况,请联系本台设备经销商。'
  379. else:
  380. return u'订购的套餐已用完。'
  381. elif cause == 2:
  382. return u'用户远程停止。'
  383. elif cause == 3:
  384. return u'管理员操作停止。'
  385. elif cause == 4:
  386. return u'经销商远程停止。'
  387. elif cause == 5:
  388. return u'用户拔掉充电器,或者插座脱落。'
  389. elif cause == 6:
  390. return u'端口功率过载,系统为了安全,关闭此充电端口。'
  391. elif cause == 7:
  392. return u'整机电压过载,系统为了安全,关闭所有充电端口。'
  393. elif cause == 8:
  394. return u'端口电流过载,系统为了安全,关闭此充电端口。'
  395. elif cause == 9:
  396. return u'整机功率超限,系统为了安全,关闭所有充电端口。'
  397. elif cause == 10:
  398. return u'检测到温度超限,系统为了安全,关闭所有充电端口。'
  399. elif cause == 11:
  400. return u'恭喜您电池已经充满'
  401. elif cause == 12:
  402. if duration < order.package.refundProtectTime:
  403. return u'充电已结束,如有异常情况,请联系设备经销商。'
  404. else:
  405. return u'订购的时间已用完'
  406. elif cause == 13:
  407. if duration < order.package.refundProtectTime:
  408. return u'充电已结束,如有异常情况,请联系本台设备经销商。'
  409. else:
  410. return u'订购的电量已用完'
  411. elif cause == 0x0E:
  412. return u"当前充电功率超过套餐允许最大功率"
  413. elif cause == 20:
  414. return u'端口功率过小。可能是电池已经充满,也可能是所接负载功率太小'
  415. return u'充电结束。'