# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import random import time import typing import simplejson as json from bson import ObjectId from mongoengine import DoesNotExist, ValidationError from apilib.monetary import VirtualCoin, RMB, Ratio from apps.web.agent.models import Agent from apps.web.common.models import TempValues from apps.web.constant import Const, DEALER_CONSUMPTION_AGG_KIND, USER_RECHARGE_TYPE from apps.web.core.device_define.changyuan import CYCardMixin from apps.web.core.payment import WithdrawGateway from apps.web.dealer.models import Dealer from apps.web.device.models import Group, Device from apps.web.eventer.base import WorkEvent from apps.web.eventer import EventBuilder from apps.web.report.ledger import Ledger from apps.web.user.models import ConsumeRecord from apps.web.user.transaction_deprecated import refund_money, refund_cash from apps.web.user.models import ServiceProgress, MyUser, RechargeRecord, Card, CardRechargeOrder, CardRechargeRecord if typing.TYPE_CHECKING: from apps.web.device.models import GroupDict logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): event_data = self.deviceAdapter.analyze_event_data(device_event['data']) if event_data is None or 'cmdCode' not in event_data: return None if 'a8id' in device_event: event_data.update({ 'a8id': device_event['a8id'] }) if device_event["cmd"] == 100: # zjl return ChangYuanCarWorkEventer(self.deviceAdapter, event_data) class ChangYuanCarWorkEventer(CYCardMixin, WorkEvent): def do(self, **args): cmdCode = self.event_data.get("cmdCode") if cmdCode == "A8": a8id = self.event_data.get("a8id", "") self._response_a8id(a8id) self.do_finished() elif cmdCode == "AE": self.do_status_change() elif cmdCode == "B1": self.do_card_start() elif cmdCode == "B2": self.do_status_report() elif cmdCode == "B3": self.do_card_refund() elif cmdCode == "F4": self.do_recharge_card() elif cmdCode == "F3": self.do_recharge_card_result() else: logger.error("error cmdCode".format(cmdCode)) def do_finished(self): cardNo = self.event_data.get("cardNo") if cardNo == "00000000": self.do_net_pay_finish() else: self.do_card_pay_finish() def do_card_start(self): cardNo = self.event_data.get("cardNo") logger.info("card <{}>start report, event_info is {}".format(cardNo, self.event_data)) if cardNo == "00000000": logger.info("cardNo is 00000000!") return card = self.update_card_dealer_and_type(self.event_data["cardNo"]) if not self.check_card_can_use(card): logger.info("[ChangYuanCarWorkEventer do_event_start] check card not can use devNo = {}".format(self.device.devNo)) self.notify_invalid_card_to_dealer(self.event_data["cardNo"], card) return self.deviceAdapter.stop() devCache = { "isStart": True, "startTime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'status': Const.DEV_WORK_STATUS_WORKING, 'finishedTime': int(time.time()) + 12 * 60 * 60 } Device.update_dev_control_cache(self.device["devNo"], devCache) if not card: return ServiceProgress.register_card_service( self.device, 0, card, ) def do_status_report(self): devCache = Device.get_dev_control_cache(self.device.devNo) or dict() orderNo, openId = devCache.get("orderNo"), devCache.get("openId") if not orderNo or not openId: logger.info("dev <{}> heart beat but not orderNo, {}".format(self.device.devNo, self.event_data)) leftMoney = RMB(self.event_data.get("leftBalance")) usedElec = self.event_data.get("usedElec") temperature = self.event_data.get("temperature") power = self.event_data.get("power") voltage = self.event_data.get("voltage") sid = self.event_data.get("sid") data = { "leftMoney": str(leftMoney), "usedElec": usedElec, "temperature": temperature, "power": power, "voltage": voltage, "sid": sid } try: record = ConsumeRecord.objects.get(orderNo=orderNo, openId=openId) # type:ConsumeRecord record.servicedInfo = data record.save() devCache.update(data) Device.update_dev_control_cache(self.device.devNo, devCache) except Exception as e: logger.error("[{} do_status_report, event = {}, error = {}]".format(self.__class__.__name__, self.event_data, e)) return def do_card_refund(self): beforeRefund = self.event_data.get("beforeRefund") refund = self.event_data.get("refund") afterRefund = self.event_data.get("afterRefund") cardNo = self.event_data.get("cardNo") if VirtualCoin(beforeRefund) + VirtualCoin(refund) != VirtualCoin(afterRefund): logger.info("bad refund event, beforeRefund is {}, refund is {} afterRefund is {}".format( beforeRefund, refund, afterRefund )) return card = self.update_card_dealer_and_type(cardNo, cardType="IC") if not card: logger.info("bad cardNo cardNo is {}".format(cardNo)) return if VirtualCoin(card.balance) != VirtualCoin(beforeRefund): logger.info( "beforeRefund isn't equal card balance, cardNo is {}, beforeRefund is {}, card balance is {}".format( cardNo, beforeRefund, card.balance )) return self.refund_money_for_card(RMB(refund), str(card.id)) self.notify_user( card.managerialOpenId, 'refund_coins', **{ 'title' : u"充电桩返费", 'backCount' : u'%s' % refund, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') } ) def do_status_change(self): status = self.event_data.get("status") Device.update_dev_control_cache(self.device.devNo, {"status": status}) def do_net_pay_finish(self): devCache = Device.get_dev_control_cache(self.device["devNo"]) openId = devCache.pop("openId", None) coins = devCache.get("coins") price = devCache.get("price") rechargeRcdId = devCache.get("rechargeRcdId") vCardId = devCache.get("vCardId") rechargeRecord = self.do_ledger(rechargeRcdId) if not openId: logger.info("ChangYuan net pay finish with no openId! {}".format(self.device["devNo"])) return nowTime = datetime.datetime.now() consumeDict = { "elec": self.event_data["usedElec"], "duration": self.event_data["chargeTime"], "coin": str(VirtualCoin(coins)), "spendMoney": str(RMB(price - self.event_data["balance"])), "reason": self.event_data["desc"], "finishedTime": datetime.datetime.strftime(nowTime, "%Y-%m-%d %H:%M:%S") } user = MyUser.objects.filter(openId = openId, groupId = self.device["groupId"]).first() group = self.device.group extra = [ {u"使用时长": u"{} 分钟".format(self.event_data['chargeTime'])}, {u"付款金额": u"{} {}".format(coins, u"金币" if not rechargeRcdId else u"元")} ] self.notify_user_service_complete( service_name=u"充电", openid=user.managerialOpenId if user else "", port=self.event_data['port'], address=group.address, reason=self.event_data["desc"], finished_time=nowTime.strftime("%Y-%m-%d %H:%M:%S"), extra=extra ) # 修改, 只有快捷支付的 才能够 进行现金退款 refundMoney = RMB(self.event_data["balance"]) if refundMoney > RMB(0): # 现金支付的 if rechargeRecord and rechargeRecord.isQuickPay: # 由于前边已经分账了 这个地方直接还是需要从经销商统计里面扣除的 refund_order = refund_cash( rechargeRecord, refundMoney, VirtualCoin(0), user = user, minus_total_consume = VirtualCoin(rechargeRecord.coins)) if refund_order: logger.info("refund cash apply success!") consumeDict.update({DEALER_CONSUMPTION_AGG_KIND.REFUNDED_CASH: refundMoney.mongo_amount}) self.notify_user( user.managerialOpenId if user else '', 'refund_coins', **{ 'title': u"退款(退款金额将于1-5个工作日内返还)", 'backCount': u'%s(元)' % refundMoney, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') } ) else: logger.info("refund cash fail") # 金币支付的并且打开了金币退款的开关 elif not vCardId and self.device.is_auto_refund: # 重新计算退币数量 防止coins不等于price refundMoney = VirtualCoin(coins) * Ratio(refundMoney.amount / RMB(price).amount) refund_money(self.device, refundMoney, openId) consumeDict.update({ DEALER_CONSUMPTION_AGG_KIND.COIN: (VirtualCoin(coins) - refundMoney).mongo_amount, DEALER_CONSUMPTION_AGG_KIND.REFUNDED_COINS: refundMoney.mongo_amount }) self.notify_user( user.managerialOpenId if user else '', 'refund_coins', **{ 'title': u"退款", 'backCount': u'%s(金币)' % refundMoney, 'finishTime': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') } ) # 剩下的 支付方式有 1.虚拟卡支付 2.金币支付但是没有打开金币支付的开关 else: logger.info("not need to refund pay type! {}".format(self.event_data)) ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "open_id": openId, "device_imei": self.device["devNo"], "port": 0, "isFinished": False }, consumeDict) # 推送订单结束信息 startTime = Device.get_dev_control_cache(self.device.devNo).get("startTime") if startTime: consumeDict.update({"startTime": startTime}) self.notify_to_sd_norther(portStr="1", consumeDict=consumeDict) Device.invalid_device_control_cache(self.device.devNo) def do_card_pay_finish(self): servicedInfo = { "cardNo": self.event_data["cardNo"], "usedElec": self.event_data["usedElec"], "duration": self.event_data["chargeTime"], "coin": str(self.event_data["payMoney"]), "cardBalance": str(self.event_data["cardBalance"]), "spendMoney": str(self.event_data["payMoney"] - self.event_data["balance"]), "reason": self.event_data["desc"], "finishedTime": datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S") } cardNo = self.event_data.get("cardNo") payMoney = self.event_data.get("payMoney") cardBalance = self.event_data.get("cardBalance") card = self.update_card_dealer_and_type(cardNo, cardType="IC") if not card: # zjl new return # zjl new card.balance = RMB(cardBalance) try: self.record_consume_for_card(card, RMB(payMoney), servicedInfo=servicedInfo) card.save() except Exception as e: logger.error(e) else: group = Group.get_group(self.device["groupId"]) self.notify_user( managerialOpenId=card.managerialOpenId, templateName="service_complete", title=u"\\n\\n结束原因:\\t\\t{reason}\\n\\n设备编号:\\t\\t{logicalCode}\\n\\n服务地址:\\t\\t{group}\\n\\n使用时长:\\t\\t{duration}分钟\\n\\n付款金额:\\t\\t{coin}\\n\\n待返费:\\t\\t{refund}".format( reason=self.event_data["desc"], logicalCode=self.device["logicalCode"], group=group.get("address", ""), duration=self.event_data["chargeTime"], coin=u"%s" % self.event_data["payMoney"], refund=u"%s 请将卡片贴近充电桩进行返费" % self.event_data["balance"] ), service=u"本次充电结束,请将充电卡放在桩的刷卡区域返费,保持静止5秒以上,直到听到 返费成功 语音后,再把卡移开!", finishTime=datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"), remark=u'谢谢您的支持' ) finally: ServiceProgress.update_progress_and_consume_rcd( self.device["ownerId"], { "device_imei": self.device["devNo"], "cardId": str(card.id), "port": 0, "isFinished": False }, {} ) # 推送订单结束信息 startTime = Device.get_dev_control_cache(self.device.devNo).get("startTime") if startTime: servicedInfo.update({"startTime": startTime}) self.notify_to_sd_norther(portStr="1", consumeDict=servicedInfo) Device.invalid_device_control_cache(self.device["devNo"]) def do_recharge_card(self): """ 卡充值的发起指令 接收到此指令之后 判断该卡是否有效 是否存在有效订单 存在的情况下下发充值数据 :return: """ cardNo = self.event_data.get("cardNo") cardBalance = self.event_data.get("cardBalance") cardType = self.event_data.get("cardType") card = self.update_card_dealer_and_type(cardNo) if not card: logger.error("not card exist, can not async card balance <{}>".format(cardNo)) return # 更新卡的余额 以设备侧的为准 if RMB(cardBalance) != card.balance: logger.info('Card<{}> balance<{}> needs to be sync !!!'.format(cardNo, card.balance)) card.balance = RMB(cardBalance) card = card.save() # orderNos 表示充值的订单 cardOrderNos 表示卡充值的订单 money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id)) # 一旦卡被冻结 立即下发 创建一张为 负数的金额订单 将卡的余额清空 if card.frozen: group = Group.get_group(card.groupId) CardRechargeOrder.new_one( openId=card.openId, cardId=str(card.id), cardNo=card.cardNo, money=RMB(0), coins=VirtualCoin(0) - VirtualCoin(coins) - VirtualCoin(card.balance), group=group, rechargeId=ObjectId(), rechargeType=u"sendCoin" ) money, coins, orderNos, cardOrderNos = CardRechargeOrder.get_to_do_list(str(card.id)) # 没有需要同步的订单的情况下 直接退出 if not orderNos: logger.info('Not card recharge record!, card is <{}>'.format(cardNo)) # self._do_offline_recharge_by_dealer(cardNo, card, cardType) return orderNos = [str(item) for item in orderNos] sid = str(random.randint(0, 0xFFFF)) TempValues.set('%s-%s' % (self.device.devNo, sid), value=json.dumps(orderNos)) TempValues.set('%s-%s-%s' % (self.device.devNo, sid, cardNo), value=json.dumps(cardOrderNos)) TempValues.set('%s-%s' % (self.device.devNo, cardNo), value=str(sid)) # 计算总的需要同步的金额 asyncMoney = RMB(cardBalance) + RMB(coins) logger.info('ready to recharge card, card is <{}>, money is <{}>'.format(cardNo, coins)) # 已经下发金额后,先将订单的状态改为结束。如果主板上报失败,再将订单状态迁移回来否则就不再变化订单状态了 try: CardRechargeOrder.update_card_order_has_finished(str(card.id)) except Exception as e: logger.debug('%s' % e) else: self.deviceAdapter._async_card_balance(cardType, cardNo, asyncMoney) def do_recharge_card_result(self): """ 卡充值的 设备回复指令 接收到此指令之后 表示订单充值已经成功了 这个时候就需要将充值订单的状态改变就行了 :return: """ asyncStatus = self.event_data.get("asyncStatus") answerSid = self.event_data.get("sid") cardBalance = self.event_data.get("cardBalance") cardNo = self.event_data.get("cardNo") card = self.update_card_dealer_and_type(cardNo) if not card: logger.error("not found card, event is <{}>".format(self.event_data)) return sid = TempValues.get('{}-{}'.format(self.device.devNo, cardNo)) if sid != answerSid: logger.error('answer sid is not equal sid <{}>-<{}>'.format(sid, answerSid)) return # 根据sid 以及卡号 查询出此次充值的所有的订单 orderNos = TempValues.get('{}-{}'.format(self.device.devNo, sid)) cardOrderNos = TempValues.get('{}-{}-{}'.format(self.device.devNo, sid, cardNo)) # 明确接收到了主板同步失败的情况下 直接退出 不在转换充值订单的问题 防止出错 if not asyncStatus: logger.error("card async not success! event data is <{}>".format(self.event_data)) return # 下面的都不会更改订单的状态 最多走售后 money, coins = RMB(0), VirtualCoin(0) # 校验金额是否是相等的 orderNos = [ObjectId(item) for item in orderNos] rds = RechargeRecord.objects.filter(ownerId=card.dealerId, id__in=orderNos) for item in rds: money += item.money coins += item.coins # 这个地方就是异常值处理了 if VirtualCoin(coins + card.balance) != VirtualCoin(cardBalance): logger.error('card pre balance not equal now balance! event is <{}>'.format(self.event_data)) return # 依次更改卡充值的订单 并创建充值订单 cardOrders = CardRechargeOrder.objects.filter(orderNo__in=cardOrderNos) for _order in cardOrders: # type: CardRechargeOrder _order.update_after_recharge_ic_card( device=self.device, sendMoney=RMB(_order.coins), preBalance=card.balance ) preBalance = card.balance card.balance = card.balance + _order.coins # 创建充值记录 CardRechargeRecord.add_record( card=card, group=Group.get_group(_order.groupId), order=_order, device=self.device ) # 保存 card.save() # 完成之后将TempValue 的key value 清空 TempValues.remove('%s-%s' % (self.device['devNo'], sid)) TempValues.remove('%s-%s' % (self.device['devNo'], cardNo)) TempValues.remove("%s-%s-%s" % (self.device["devNo"], sid, cardNo)) def _response_a8id(self, a8id): self.deviceAdapter._response_a8id(a8id) def update_card_dealer_and_type(self, cardNo, cardType='IC', isHaveBalance=True, balance=None): """ 更新卡的状态 重写目的 如果卡不存在 立即下发停止充电指令 而不是新建一张卡 :param cardNo: :param cardType: :param isHaveBalance: :param balance: :return: """ dealer = Dealer.objects.get(id=self.device["ownerId"]) agent = Agent.objects.get(id=dealer.agentId) if not agent: logger.error('agent is not found, agentId=%s' % dealer.agentId) return try: card = Card.objects.get(cardNo=cardNo, agentId=dealer.agentId) # 如果卡没有被绑定,这个时候检查下绑定关系。如果卡已经被某个经销商认领了,就不要刷新,不要动了 if card.cardType and card.dealerId and card.devNo: return card card.dealerId = self.device['ownerId'] card.devNo = self.device['devNo'] card.cardType = cardType card.devTypeCode = self.device['devType']['code'] card.isHaveBalance = isHaveBalance return card.save() except DoesNotExist: return except Exception as e: logger.exception(e) return