123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- # -*- 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
|