# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import typing from typing import TYPE_CHECKING from apilib.monetary import RMB from apilib.utils_sys import memcache_lock from apps.web.constant import ErrorCode, START_DEVICE_STATUS, DEALER_CONSUMPTION_AGG_KIND from apps.web.device.models import Group, Device from apps.web.eventer import EventBuilder from apps.web.eventer.weifuleCommon import WeiFuLeStatusEvent, WeiFuLePolicyProcess from apps.web.exceptions import UserServerException from apps.web.user.constant2 import ConsumeOrderServiceItem from apps.web.user.models import ServiceProgress, Card, CardRechargeOrder, CardRechargeRecord, UserVirtualCard from apps.web.user.utils import get_consume_order from apps.web.user.utils2 import ConsumeOrderStateEngine, generate_net_payment, generate_card_payment, generate_refund from apps.web.utils import set_start_key_status if typing.TYPE_CHECKING: from apps.web.user.models import ConsumeRecord logger = logging.getLogger(__name__) created_order_32 = 32 exec_order_33 = 33 finished_order_34 = 34 id_card_request_35 = 35 card_recharge_order_36 = 36 status_change_event_44 = 44 ic_consume_event_48 = 48 # ic卡花钱的时候,会上报此事件 part_sn_event = 49 # 组件上报相关信息 card_is_normal = 1 card_not_in_db = 2 card_is_forzen = 3 card_has_not_order = 4 # IC卡适用 card_less_balance = 5 # ID卡适用 card_type_is_ic = 6 # IC卡不允许当做ID卡使用 no_load_code = 7 if TYPE_CHECKING: from apps.web.core.adapter.base import SmartBox from apps.web.core.adapter.weifule_policy import POLICYBox class builder(EventBuilder): def __getEvent__(self, device_event): event_data = self.deviceAdapter.analyze_event_data(device_event['data']) if event_data is None: return None if event_data['fun_code'] in [ created_order_32, exec_order_33, finished_order_34, id_card_request_35, card_recharge_order_36 ]: return ChargingSocketWorkEvent(self.deviceAdapter, event_data) if event_data['fun_code'] in [status_change_event_44]: return WeiFuLeStatusEvent(self.deviceAdapter, device_event) class ChargingSocketWorkEvent(WeiFuLePolicyProcess): def __init__(self, smartBox, event_data): # type:(SmartBox,dict)->None super(ChargingSocketWorkEvent, self).__init__(smartBox, event_data) self.deviceAdapter = smartBox # type: POLICYBox def create_progress_for_socket_order(self, consumeRcd, devInfo): try: port = int(devInfo['port']) progress = ServiceProgress.objects.filter(device_imei=self.device.devNo, port=port, devTypeCode=self.device.devTypeCode).first() if not progress: progress = ServiceProgress(device_imei=self.device.devNo, port=port, devTypeCode=self.device.devTypeCode) progress.start_time = devInfo['create_time'] progress.finished_time = devInfo['create_time'] + 60 * 60 * 12 progress.isFinished = False else: if devInfo['id'] in progress.consumes: return if progress.isFinished is False: progress.finished_time += 60 * 60 * 12 else: progress.consumes = [] progress.start_time = devInfo['create_time'] progress.finished_time = devInfo['create_time'] + devInfo.get('amount_time', 60 * 60 * 12) progress.isFinished = False progress.open_id = consumeRcd.openId progress.consumes.append(devInfo['id']) progress.status = 'running' progress.save() except Exception as e: logger.exception(e) def do(self, **args): devNo = self.device['devNo'] funCode = self.event_data.get('fun_code') order = self.event_data.get('order', {}) logger.info('weifule charging event detected, devNo={}, event = {}'.format(devNo, self.event_data)) # 刷卡查询余额的上报 if funCode == id_card_request_35: return self.response_id_card() with memcache_lock(key='%s-%s-%s-finished' % (devNo, order.get('id'), funCode), value='1', expire=120) as acquired: if not acquired: logger.info('weifule charging event is doing !!!, devNo={}'.format(devNo)) return self._do_ack_order(order) try: if funCode == created_order_32: self._do_created_order_32(order) elif funCode == exec_order_33: self._do_exec_order_33(order) elif funCode == finished_order_34: if order["order_type"] == 'card_charge': self.update_card_recharge_for_success_event(order['id'], RMB(order['balance'] / 100.0)) elif order["order_type"] == 'card_refund': self.end_for_card_refund(order) else: self._do_finished_order_34(order) elif funCode == card_recharge_order_36: # 如果是卡充值,直接回复订单 self.deal_with_ic_charge_event() except Exception as e: logger.exception(e) logger.info('deal order is finished < funCode: {} order: {} >'.format(funCode, order.get('id'))) def translate_reason(self, order): cause = order.get('cause') if cause == 1: if self.duration < self.refundProtectionTime: return u'充电已结束,如有异常情况,请联系本台设备经销商。' else: return u'订购的套餐已用完。' elif cause == 2: return u'用户远程停止。' elif cause == 3: return u'管理员操作停止。' elif cause == 4: return u'经销商远程停止。' elif cause == 5: return u'用户拔掉充电器,或者插座脱落。' elif cause == 6: return u'端口功率过载,系统为了安全,关闭此充电端口。' elif cause == 7: return u'整机电压过载,系统为了安全,关闭所有充电端口。' elif cause == 8: return u'端口电流过载,系统为了安全,关闭此充电端口。' elif cause == 9: return u'整机功率超限,系统为了安全,关闭所有充电端口。' elif cause == 10: return u'检测到温度超限,系统为了安全,关闭所有充电端口。' elif cause == 11: return u'恭喜您电池已经充满' elif cause == 12: if self.duration < self.refundProtectionTime: return u'充电已结束,如有异常情况,请联系设备经销商。' else: return u'订购的时间已用完' elif cause == 13: if self.duration < self.refundProtectionTime: return u'充电已结束,如有异常情况,请联系本台设备经销商。' else: return u'订购的电量已用完' elif cause == 0x0E: return u"当前充电功率超过套餐允许最大功率" elif cause == 20: return u'端口功率过小。可能是电池已经充满,也可能是所接负载功率太小' return u'充电结束。' def deal_with_ic_charge_event(self): cardNo = self.event_data['card_no'] card = self.update_card_dealer_and_type(cardNo, 'IC') if not card: return self.deviceAdapter.response_card_charge_result(self.event_data['card_no'], card_not_in_db) elif card.frozen: return self.deviceAdapter.response_card_charge_result(self.event_data['card_no'], card_is_forzen) preBalance = card.balance rechargeOrder = CardRechargeOrder.get_last_to_do_one(str(card.id)) if rechargeOrder: self.recharge_ic_card(card=card, preBalance=preBalance, rechargeType='append', order=rechargeOrder, need_verify=False) else: return self.deviceAdapter.response_card_charge_result(self.event_data['card_no'], card_has_not_order) def recharge_ic_card(self, card, preBalance, rechargeType, order, need_verify=True): # type:(Card, RMB, str, CardRechargeOrder, bool)->bool """ # rechargeType有两种,一种是用直接覆写overwrite的方式,一种是append追加钱的方式。 # 不同的的设备,充值的方式还不一样.注意:money是实际用户付的钱,coins是给用户充值的钱,比如付10块(money),充15(coins)。 :param card: :param preBalance: :param rechargeType: :param order: :return: """ if not order or order.coins == RMB(0): return False status = Card.get_card_status(str(card.id)) if status == 'busy': return False Card.set_card_status(str(card.id), 'busy') try: # IC卡需要下发到设备,设备写卡,把余额打入卡中 if rechargeType == 'overwrite': sendMoney = preBalance + order.coins else: sendMoney = order.coins # 先判断order最近一次充值是否OK. 满足三个条件才认为上次充值成功: # 1、操作时间已经超过三天 # 2、操作结果是串口超时, 即result == 1 # 3、当前余额大于最后一次充值操作的充值前余额 if need_verify and len(order.operationLog) > 0: log = order.operationLog[-1] result = log['result'] time_delta = (datetime.datetime.now() - log['time']).total_seconds() last_pre_balance = RMB(log['preBalance']) if (result == ErrorCode.DEVICE_CONN_FAIL or result == ErrorCode.BOARD_UART_TIMEOUT) \ and (time_delta > 3 * 24 * 3600 or preBalance > last_pre_balance): logger.debug('{} recharge verify result is finished.'.format(repr(card))) order.update_after_recharge_ic_card(device=self.device, sendMoney=sendMoney, preBalance=preBalance, result=ErrorCode.SUCCESS, description=u'充值校验结束') CardRechargeRecord.add_record( card=card, group=Group.get_group(order.groupId), order=order, device=self.device) return False try: operation_result, balance = self.deviceAdapter.recharge_card(card.cardNo, sendMoney, orderNo=str(order.id)) order.update_after_recharge_ic_card(device=self.device, sendMoney=sendMoney, preBalance=preBalance, syncBalance=balance, result=operation_result['result'], description=operation_result['description']) if operation_result['result'] != ErrorCode.SUCCESS: return False if not balance: balance = preBalance + order.coins CardRechargeRecord.add_record( card=card, group=Group.get_group(order.groupId), order=order, device=self.device) # 刷新卡里面的余额 card.balance = balance card.lastMaxBalance = balance card.save() return True except Exception as e: order.update_after_recharge_ic_card(device=self.device, sendMoney=sendMoney, preBalance=preBalance, syncBalance=balance, result=ErrorCode.EXCEPTION, description=e.message) return False except Exception as e: logger.exception(e) return False finally: Card.set_card_status(str(card.id), 'idle') def prepaid_end_for_app_start(self, order): """ :param order: 主板上报的 object order 信息 """ # 虚拟卡支付的 if self.consumeRcd and self.consumeRcd.virtual_card_id: ue = order["elec"] / 1000 * 1000 ut = order["time"] / 60.0 vcardRcd = UserVirtualCard.objects.get(id=self.consumeRcd.virtual_card_id) # type: UserVirtualCard modified, consumeTotal, consumeDay = vcardRcd.clear_frozen_quota(str(self.consumeRcd.id), ut, ue, 0) # 支付完成之后 穿件一笔消费记录 if not modified: return vcardRcd.new_consume_record(self.device, self.consumeRcd.user, consumeTotal) else: super(ChargingSocketWorkEvent, self).prepaid_end_for_app_start(order) # ---------------------------------------- 继承重写 防止误判之前的逻辑 -------------------------------------------- def pay_apps_start_by_32(self, order, callbackSP=None): """ 此函数会对订单进行扣款(补扣或者抢锁扣款,依据上报的事件) 一般callbackSP为创建服务进程函数 """ consumeRcd = get_consume_order(orderNo=order["id"]) # type: ConsumeRecord # 检查订单是否完成 if consumeRcd is None: logger.info('[pay_apps_start_by_32] weifule do 32, not find order = {}, devNo = {}'.format(order['id'], self.device.devNo)) return # 检测订单是否是合法状态 if consumeRcd.status not in [ consumeRcd.Status.WAIT_CONF, consumeRcd.Status.UNKNOWN, consumeRcd.Status.TIMEOUT ]: logger.info('[pay_apps_start_by_32] weifule do 32, order <{}> status = '.format(consumeRcd.orderNo, consumeRcd.orderNo)) return # 走到此处 一般是订单状态还在异常状态 此时直接执行 createTime = order["create_time"] deviceStartTime = datetime.datetime.fromtimestamp(createTime).strftime("%Y-%m-%d %H:%M:%S") ConsumeOrderStateEngine(consumeRcd).to_running({"deviceStartTime": deviceStartTime}) set_start_key_status(start_key=consumeRcd.orderNo, state=START_DEVICE_STATUS.FINISHED, order_id=str(consumeRcd.id)) consumeRcd.frozen_payer_balance() def _do_finished_order_34(self, order): """ 处理订单的结束消息 """ # 查找订单 consumeRcd = get_consume_order(orderNo=order['id']) # type: ConsumeRecord if not consumeRcd: logger.info('[_do_finished_order_34], not find order = {}, devNo = {}'.format(order['id'], self.device.devNo)) return # 检查订单的状态 if consumeRcd.status not in [consumeRcd.Status.RUNNING]: logger.info('[_do_finished_order_34], order <{}> status not normal'.format(order['id'])) return # 组建订单的消费信息 duration = round(order['time'] / 60.0, 1) elec = round(order['elec'] / 1000000.0, 3) power = round(order.get('power', 0), 1) reason = self._get_reason(order.get("cause"), duration, consumeRcd) consumeDict = { ConsumeOrderServiceItem.ELEC: elec, ConsumeOrderServiceItem.DURATION: duration, ConsumeOrderServiceItem.MAX_POWER: power, ConsumeOrderServiceItem.REASON: reason, ConsumeOrderServiceItem.END_TIME: datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ConsumeOrderServiceItem.SPEND: RMB(self.event_data["order"]['money'] * 0.01) } # 将订单的状态置为 结束 并记录服务信息 ConsumeOrderStateEngine(consumeRcd).to_end(consumeDict) Device.clear_port_control_cache(self.device.devNo, str(order["port"])) # 以下步骤 实际上可以从event里面剥离开 直接进入订单处理中心排队完成 时间关系 暂时先放在event里面 consumeRcd.reload() # 后支付的情况 需要将订单的金额算出来 并且生成支付信息 然后进行相应的支付 if consumeRcd.isPostPaid: # 为后付费的订单添加订单的金额 if consumeRcd.isFree: consumeRcd.price = RMB(0) else: if consumeRcd.service.duration <= consumeRcd.package.refundProtectTime: consumeRcd.price = RMB(0) else: # 最小消费金额 和 实际花费金额中选一个 consumeRcd.price = max(consumeRcd.service.spendMoney, consumeRcd.package.minFee) consumeRcd.save() # 根据启动方式的不同 为后付费的订单生成相应的支付条件 try: if consumeRcd.isStartNetPay: # 网络支付的生成 payment = generate_net_payment(consumeRcd) elif consumeRcd.isStartCardPay: # 实体卡 支付的支付 payment = generate_card_payment(consumeRcd) else: logger.error("[_do_finished_order_34], postPaid order <{}> error start type".format(order['id'])) return except UserServerException: payment = None # 尝试一次支付 if payment: consumeRcd.update_payment(payment) consumeRcd.frozen_payer_balance() consumeRcd.clear_payer_frozen() else: # 预支付 保护时间内 全额退费 if consumeRcd.service.duration <= consumeRcd.package.refundProtectTime: refundMoney = consumeRcd.price else: if consumeRcd.package.autoRefund: refundMoney = consumeRcd.price - consumeRcd.service.spendMoney else: refundMoney = RMB(0) consumeRcd.clear_payer_frozen(refundMoney) # 尝试变更订单的状态 同时 consumeRcd.reload() ConsumeOrderStateEngine(consumeRcd).to_finished() def _do_finished_order(self): pass @staticmethod def _get_reason(cause, duration, order): # type: (int, float, ConsumeRecord) -> basestring if cause == 1: if duration < order.package.refundProtectTime: return u'充电已结束,如有异常情况,请联系本台设备经销商。' else: return u'订购的套餐已用完。' elif cause == 2: return u'用户远程停止。' elif cause == 3: return u'管理员操作停止。' elif cause == 4: return u'经销商远程停止。' elif cause == 5: return u'用户拔掉充电器,或者插座脱落。' elif cause == 6: return u'端口功率过载,系统为了安全,关闭此充电端口。' elif cause == 7: return u'整机电压过载,系统为了安全,关闭所有充电端口。' elif cause == 8: return u'端口电流过载,系统为了安全,关闭此充电端口。' elif cause == 9: return u'整机功率超限,系统为了安全,关闭所有充电端口。' elif cause == 10: return u'检测到温度超限,系统为了安全,关闭所有充电端口。' elif cause == 11: return u'恭喜您电池已经充满' elif cause == 12: if duration < order.package.refundProtectTime: return u'充电已结束,如有异常情况,请联系设备经销商。' else: return u'订购的时间已用完' elif cause == 13: if duration < order.package.refundProtectTime: return u'充电已结束,如有异常情况,请联系本台设备经销商。' else: return u'订购的电量已用完' elif cause == 0x0E: return u"当前充电功率超过套餐允许最大功率" elif cause == 20: return u'端口功率过小。可能是电池已经充满,也可能是所接负载功率太小' return u'充电结束。'