# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time import typing from mongoengine import DoesNotExist, ValidationError from apilib.monetary import Ratio, VirtualCoin, RMB from apps.web.constant import DeviceCmdCode, Const, DEALER_CONSUMPTION_AGG_KIND, USER_RECHARGE_TYPE from apps.web.core.device_define.changyuan import CYCardMixin from apps.web.core.networking import MessageSender from apps.web.core.payment import WithdrawGateway from apps.web.device.models import Device, Group 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, RechargeRecord, MyUser logger = logging.getLogger(__name__) class builder(EventBuilder): def __getEvent__(self, device_event): if device_event['cmd'] == 100: 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.get('a8id') }) return ChangYuanCaiWorkEventer(self.deviceAdapter, event_data) class ChangYuanCaiWorkEventer(CYCardMixin, WorkEvent): def _answer_a8(self, a8id): MessageSender.send(self.device, DeviceCmdCode.OPERATE_DEV_NO_RESPONSE, { "IMEI": self.device["devNo"], "funCode": "FA", "data": "", "a8id": a8id }) def do(self, **args): devNo = self.device["devNo"] logger.info("chang yuan car event detected, devNo=%s, info=%s" % (devNo, self.event_data)) # 充电停止状态 if self.event_data["cmdCode"] == "A8": # 收到A8 指令后需要回复模块,将其从队列中清除 self._answer_a8(self.event_data.get("a8id", "")) cardNo = self.event_data["cardNo"] self.do_finish(cardNo) # 待机情况下枪把连接状态 事件 elif self.event_data["cmdCode"] == "AE": logger.info("Charging pile connected event") status = self.event_data["status"] if status == "B1": Device.update_dev_control_cache(self.device["devNo"], {"status": Const.DEV_WORK_STATUS_CONNECTED}) elif status == "B5": Device.update_dev_control_cache(self.device["devNo"], {"status": Const.DEV_WORK_STATUS_IDLE}) else: logger.error("undefined status, dev is %s, status is %s" % (devNo, status)) elif self.event_data["cmdCode"] == "B1": cardNo = self.event_data["cardNo"] if cardNo != "00000000": # 判断是否是失窃卡 不存在此卡说明卡未被录入且第一次使用,需要创建 card = self.update_card_dealer_and_type(self.event_data["cardNo"]) if not self.check_card_can_use(card): logger.info("[ChangYuanCaiWorkEventer 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, ) # 心跳连接,每3分钟一次,及时更新订单的当前使用消费情况 elif self.event_data["cmdCode"] == "B2": devInfo = Device.get_dev_control_cache(self.device["devNo"]) or dict() openId = devInfo.get("openId") orderNo = devInfo.get("orderNo") if not openId or not orderNo: logger.info("dev <{}> heart beat but not openId, {}".format(self.device.devNo, self.event_data)) return leftBalance = RMB(self.event_data["leftBalance"]) # A6指令会查询 剩余金额 在经销商查看端口的时候 这个地方也顺便更新一下订单的 try: record = ConsumeRecord.objects.get(orderNo=orderNo, openId=openId) # type:ConsumeRecord record.servicedInfo["leftMoney"] = str(leftBalance) record.save() devInfo.update({"leftMoney": str(leftBalance)}) Device.update_dev_control_cache(self.device.devNo, devInfo) except Exception as e: logger.exception(e) return elif self.event_data["cmdCode"] == "B3": 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') } ) else: logger.error("unknown command , so event doesn't work!") def do_net_pay_finish(self): """ 网络支付 结束事件处理, 现金返还原路 金币退还金币 现金退还现金 2021-05-27修改流程 将退款和分账分开 进来之后 如果该笔订单 有充值金额 并且没有分账 直接先分账 后面退费流程和主退款流程统一 :return: """ 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["electricNum"], "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 logger.info("ChangYuan net pay finish and start to notify user! {}".format(self.device["devNo"])) 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 ) # 走到这个地方的时候 只要是支付现金的订单 都已经分账了 所以可以不论条件 只要存在rechargeRecord 直接执行退款 但是如果是金币的 则需要判断金币是否退钱 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_ic_card_pay_finish(self): """ 处理刷卡结束事件 :return: """ # 离线卡支付,此处只对离线卡的 金额发生变化 其余均只做日志类处理,不污染用户消费记录数据 servicedInfo = { "cardNo": self.event_data["cardNo"], "elec": self.event_data["electricNum"], # 充电电量 "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 Device.invalid_device_control_cache(self.device.devNo) return # zjl 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: extra = [ {u"使用卡号": u"{}".format(cardNo)}, {u"使用时长": u"{} 分钟".format(self.event_data['chargeTime'])}, {u"付款金额": u"{} 元".format(self.event_data["payMoney"])}, {u"待返费": u"{} 元 请将卡片贴近充电桩返费".format(self.event_data['balance'])} ] self.notify_user_service_complete( service_name=u"充电", openid=card.managerialOpenId, port=self.event_data['port'], address=self.device.group.address, reason=self.event_data["reason"], finished_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), extra=extra ) 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_finish(self, cardNo): if cardNo == "00000000": self.do_net_pay_finish() else: self.do_ic_card_pay_finish()