utils.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import base64
  4. import copy
  5. import datetime
  6. import hashlib
  7. import hmac
  8. import json
  9. import logging
  10. import time
  11. from calendar import monthrange
  12. from bson.objectid import ObjectId
  13. from dateutil.relativedelta import relativedelta
  14. from django.conf import settings
  15. from django.utils.module_loading import import_string
  16. from mongoengine import DoesNotExist
  17. from typing import Any, Tuple, TYPE_CHECKING, Optional, Union
  18. from apilib.monetary import Percent, RMB, Ratio
  19. from apps.web.agent.models import Agent
  20. from apps.web.core.exceptions import InsufficientFundsError
  21. from apps.web.dealer.constant import TodoTypeEnum, TodoDone
  22. from apps.web.dealer.define import DEALER_INCOME_SOURCE, DEALER_INCOME_TYPE
  23. from apps.web.dealer.models import DealerRechargeRecord, ItemType, Dealer, VirtualCard, TodoMessage
  24. from apps.web.device.models import Group, Device, StockRecord
  25. from apps.web.management.models import Manager
  26. from apps.web.utils import DealerLoginPageResponseRedirect, DealerMainPageResponseRedirect, \
  27. SubAccountLoginResponseRedirect, concat_front_end_url
  28. if TYPE_CHECKING:
  29. from library.memcache_backend import CustomizedMemcachedCacheBackend
  30. from apps.web.core.payment import WechatPaymentGateway
  31. from apps.web.device.models import DeviceRentOrder
  32. logger = logging.getLogger(__name__)
  33. def gen_login_response(agentId):
  34. response = DealerLoginPageResponseRedirect(register = True)
  35. if agentId:
  36. agent = Agent.objects(id = str(agentId)).get()
  37. if agent.featureToggles.has_key('forbiddenDealerRegister') and agent.featureToggles['forbiddenDealerRegister']:
  38. response = DealerLoginPageResponseRedirect(register = False)
  39. if agent.is_primary:
  40. response.set_cookie(key = 'dealer_login_agentid', value = '', max_age = 0,
  41. expires = 'Thu, 01-Jan-1970 00:00:00 GMT', domain = settings.COOKIE_DOMAIN)
  42. response.set_cookie(key = 'dealer_login_agentid', value = '', max_age = 0,
  43. expires = 'Thu, 01-Jan-1970 00:00:00 GMT')
  44. response.set_cookie(key = 'dealer_login_managerid', value = agent.managerId,
  45. max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN)
  46. else:
  47. response.set_cookie(key = 'dealer_login_managerid', value = '', max_age = 0,
  48. expires = 'Thu, 01-Jan-1970 00:00:00 GMT', domain = settings.COOKIE_DOMAIN)
  49. response.set_cookie(key = 'dealer_login_managerid', value = '', max_age = 0,
  50. expires = 'Thu, 01-Jan-1970 00:00:00 GMT')
  51. response.set_cookie(key = 'dealer_login_agentid', value = str(agent.id),
  52. max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN)
  53. response = agent.put_cookie(response)
  54. return response
  55. def gen_subaccount_login_response(agentId):
  56. response = SubAccountLoginResponseRedirect(agentId)
  57. if agentId:
  58. response.set_cookie(key = 'dealer_login_agentid', value = str(agentId),
  59. max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN)
  60. return response
  61. def gen_home_response(agentId):
  62. response = DealerMainPageResponseRedirect()
  63. response = Agent.record_cookie(agentId, response)
  64. return response
  65. INCOME_SOURCE_ALIASES = {
  66. DEALER_INCOME_SOURCE.RECHARGE: 'apps.web.common.proxy.ClientRechargeModelProxy',
  67. DEALER_INCOME_SOURCE.RECHARGE_CARD: 'apps.web.common.proxy.ClientRechargeModelProxy',
  68. DEALER_INCOME_SOURCE.RECHARGE_VIRTUAL_CARD: 'apps.web.common.proxy.ClientRechargeModelProxy',
  69. DEALER_INCOME_SOURCE.INSURANCE: 'apps.web.common.proxy.ClientRechargeModelProxy',
  70. DEALER_INCOME_SOURCE.AD: 'apps.web.ad.models.AdRecord',
  71. DEALER_INCOME_SOURCE.REDPACK: 'apps.web.common.proxy.ClientRechargeModelProxy',
  72. DEALER_INCOME_SOURCE.AUTO_SIM: 'apps.web.common.proxy.ClientRechargeModelProxy',
  73. DEALER_INCOME_SOURCE.REFUND_CASH: 'apps.web.common.proxy.ClientRechargeModelProxy'
  74. }
  75. _income_source = {}
  76. def resolve_income_source(channel = None):
  77. try:
  78. channel = INCOME_SOURCE_ALIASES[channel]
  79. except KeyError:
  80. if '.' not in channel and ':' not in channel:
  81. from kombu.utils.text import fmatch_best
  82. alt = fmatch_best(channel, INCOME_SOURCE_ALIASES)
  83. if alt:
  84. raise KeyError(
  85. 'No such income channel: {0}. Did you mean {1}?'.format(
  86. channel, alt))
  87. raise KeyError('No such transport: {0}'.format(channel))
  88. return import_string(channel)
  89. def get_income_source_cls(transport = None):
  90. # type: (str)->Any
  91. if transport not in _income_source:
  92. _income_source[transport] = resolve_income_source(transport)
  93. return _income_source[transport]
  94. # keys
  95. DEALER_DAILY_INCOME_CACHE_KEY = 'd-income:{dealerId}:source:{source}:date:{year:d}-{month:d}-{day:d}'
  96. DEALER_MONTHLY_INCOME_CACHE_KEY = 'd-income:{dealerId}:source:{source}:date:{year:d}-{month:d}'
  97. DEALER_CONSUMPTION_CACHE_KEY = 'd-consumption:{dealerId}:source:{source}:date:{date}'
  98. def dealer_daily_income_cache_key(dealerId, source, year, month, day):
  99. # type: (ObjectId, str, int, int, int)->str
  100. """
  101. :param dealerId:
  102. :param source:
  103. :param year:
  104. :param month:
  105. :param day:
  106. :return:
  107. """
  108. return DEALER_DAILY_INCOME_CACHE_KEY.format(dealerId = str(dealerId), source = str(source), year = year,
  109. month = month, day = day)
  110. def dealer_monthly_income_cache_key(dealerId, source, year, month):
  111. """
  112. :param dealerId:
  113. :param source:
  114. :param year:
  115. :param month:
  116. :return:
  117. """
  118. return DEALER_MONTHLY_INCOME_CACHE_KEY.format(dealerId = str(dealerId), source = str(source), year = year,
  119. month = month)
  120. def get_month_range(year, month):
  121. # type: (int, int)->Tuple[str, str]
  122. """
  123. :param year:
  124. :param month:
  125. :return:
  126. """
  127. DATE_FMT_TEXT = '{year}-{month}-{day}'
  128. _, end = monthrange(year = int(year), month = int(month))
  129. return (
  130. # start
  131. DATE_FMT_TEXT.format(year = year, month = month, day = 1),
  132. # end
  133. DATE_FMT_TEXT.format(year = year, month = month, day = end)
  134. )
  135. def dealer_consumption_cache_key(dealerId, source, date):
  136. # type: (ObjectId, str, str)->str
  137. """
  138. :param dealerId:
  139. :param source:
  140. :param date:
  141. :return:
  142. """
  143. return DEALER_CONSUMPTION_CACHE_KEY.format(dealerId = str(dealerId), source = str(source), date = str(date))
  144. def get_devices_count_by_owner(cache, ownerId):
  145. # type: (CustomizedMemcachedCacheBackend, str)->Tuple[int, int, int]
  146. """
  147. :param ownerId:
  148. :return:
  149. """
  150. def get_devices_by_dealer(dealer):
  151. # type:(Dealer)->list
  152. groupIds = Group.get_group_ids_of_dealer_and_partner(str(dealer.id))
  153. devices = Device.get_devices_by_group(groupIds, verbose = True)
  154. return devices.values()
  155. def is_pure_partner(agentId):
  156. # type:(str)->bool
  157. agent = Agent.objects(id = agentId).get() # type: Agent
  158. manager = Manager.objects(id = agent.managerId).get() # type: Manager
  159. isPurePartner = manager.supports('partners_are_pure')
  160. return isPurePartner
  161. def create_partner(username, password, agentId, nickname, annualTrafficCost, agentProfitShare):
  162. # type:(str, str, str, str, Percent)->Dealer
  163. """
  164. :param username:
  165. :param password:
  166. :param agentId:
  167. :param nickname:
  168. :param agentProfitShare:
  169. :return:
  170. """
  171. return Dealer.create_user(username = username,
  172. password = password,
  173. agentId = agentId,
  174. nickname = nickname,
  175. adShow = True,
  176. noAdPolicy = 'banner',
  177. annualTrafficCost = annualTrafficCost,
  178. agentProfitShare = agentProfitShare,
  179. isPurePartner = is_pure_partner(agentId))
  180. def update_partner(dealerId, agentId, **kwargs):
  181. """
  182. :param dealerId:
  183. :param agentId:
  184. :param kwargs:
  185. :return:
  186. """
  187. return Dealer.update_dealer(dealerId, isPurePartner = is_pure_partner(agentId), **kwargs)
  188. def consume_stock(devObj, consumeCount, detail = ''):
  189. countAll = 0
  190. devNo = devObj.devNo
  191. consumptionQuantity = getattr(devObj, 'consumptionQuantity', 0)
  192. consumptionQuantity += consumeCount
  193. for k, v in devObj.stockDetailDict.items(): # 兑币机实际只有一个品类,硬币
  194. if v >= consumeCount:
  195. v = v - consumeCount
  196. else:
  197. return False
  198. devObj.stockDetailDict[k] = v
  199. countAll += v
  200. try:
  201. itemObj = ItemType.objects.get(id = k)
  202. except Exception, e:
  203. logger.info('get itemtype error=%s' % e)
  204. return False
  205. more = '%s' % itemObj.title if not detail else '%s(%s)' % (itemObj.title, detail)
  206. if len(devObj.stockDetailDict) == 0: # 要考虑兑币机这种没有品类的设备
  207. countAll = getattr(devObj, 'quantity') - consumeCount
  208. more = u'出币'
  209. try:
  210. devObj.save()
  211. except Exception, e:
  212. logger.info('save obj error,devNo=%s' % devNo)
  213. return False
  214. result = Device.update_field(dev_no = devNo, update = True, quantity = countAll,
  215. consumptionQuantity = consumptionQuantity)
  216. if not result:
  217. logger.info('update field failed,devNo=%s' % devNo)
  218. return False
  219. stockTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  220. StockRecord.get_collection().insert(
  221. {'logicCode': devObj.logicalCode, 'imei': devNo, 'stockType': 'consume', 'stockTime': stockTime,
  222. 'number': consumeCount, 'more': more})
  223. return True
  224. # 经销商创建虚拟卡(线下)
  225. class VirtualCardBuilder(object):
  226. """
  227. #创建虚拟卡空卡可以指定数量
  228. """
  229. @staticmethod
  230. def create_virtual_card(dealer, vCard, attachParas = {}, accounting = False):
  231. """
  232. 创建虚拟卡号,此卡的
  233. """
  234. from apps.web.user.models import UserVirtualCard
  235. num = attachParas.get('number')
  236. cardId = attachParas.get('card_id')
  237. lis = []
  238. for i in xrange(int(num)):
  239. user_card = UserVirtualCard(
  240. cardNo = UserVirtualCard.make_no(),
  241. cardTypeId = str(cardId),
  242. openIds = [],
  243. cardName = vCard.cardName,
  244. ownerOpenId = vCard.ownerId,
  245. # nickname=dealer.nickname,
  246. dealerId = vCard.ownerId,
  247. groupIds = vCard.groupIds,
  248. devTypeList = vCard.devTypeList,
  249. price = vCard.price,
  250. periodDays = vCard.periodDays,
  251. expiredTime = datetime.datetime.now() + datetime.timedelta(seconds = vCard.periodDays * 24 * 3600),
  252. startTime = datetime.datetime.now(),
  253. dayQuota = vCard.dayQuota,
  254. userLimit = 1,
  255. quota = vCard.quota,
  256. # userDesc=vCard.userDesc,
  257. remark = '经销商开卡',
  258. status = 'nonactivated'
  259. )
  260. user_card.nickname = user_card.cardNo
  261. user_card.save()
  262. lis.append(user_card.cardNo)
  263. return lis
  264. @staticmethod
  265. def find_dealer_virtual_card(dealerId, status = 'nonactivated'):
  266. """
  267. #查询经销商开的空卡
  268. :param dealerId:
  269. :param status:
  270. :return:
  271. """
  272. from apps.web.user.models import UserVirtualCard
  273. virtual_cards = UserVirtualCard.objects.filter(ownerOpenId = dealerId, dealerId = dealerId, status = status)
  274. return virtual_cards
  275. @staticmethod
  276. def active_virtual_card(VirtualCardId):
  277. """
  278. 经销商给实体卡绑定后激活卡片
  279. :param VirtualCardId:
  280. :return:
  281. """
  282. result = False
  283. from apps.web.user.models import UserVirtualCard
  284. try:
  285. virtual_card = UserVirtualCard.objects.get(id = VirtualCardId)
  286. card_model = VirtualCard.objects.get(id = virtual_card.cardTypeId)
  287. except Exception as e:
  288. logger.info('VirtualCardBuilder_no this virtual_card cardNo:%s' % VirtualCardId)
  289. return result
  290. virtual_card.startTime = datetime.datetime.now
  291. virtual_card.expiredTime = datetime.datetime.now() + datetime.timedelta(
  292. seconds = card_model.periodDays * 24 * 3600)
  293. virtual_card.status = 'normal'
  294. virtual_card.save()
  295. return True
  296. @staticmethod
  297. def unbind_virtual_card(card):
  298. """
  299. 解绑虚拟卡,并改变虚拟卡状态
  300. :param card:
  301. :return:
  302. """
  303. result = False
  304. try:
  305. virtual_card = card.bound_virtual_card
  306. if datetime.datetime.now() > virtual_card.expiredTime:
  307. virtual_card.status = 'expired'
  308. else:
  309. virtual_card.status = 'used'
  310. virtual_card.save()
  311. card.boundVirtualCardId = None
  312. card.save()
  313. result = True
  314. except Exception as e:
  315. logger.error('card:{},error={}'.format(card.cardNo, e))
  316. pass
  317. return result
  318. def create_dealer_sim_charge_order(payment_gateway, dealer, devItems, payType = "pay", can_used_balance = None):
  319. from apps.web.common.transaction import DealerPaySubType
  320. from apps.web.core import ROLE
  321. sub_type = DealerPaySubType.SIM_CARD
  322. if payType == 'auto':
  323. sub_type = DealerPaySubType.AUTO_SIM_CARD
  324. elif payType == 'manual':
  325. sub_type = DealerPaySubType.MANUAL_SIM_CARD
  326. subServiceName = DealerRechargeRecord.get_product(sub_type)['desc']
  327. dealerId = dealer.bossId
  328. curr_annualTrafficCost = dealer.annualTrafficCost
  329. total_fee = int(0)
  330. total_agent_earning_fee = int(0)
  331. total_manager_earning_fee = int(0)
  332. agent = Agent.objects(id = dealer.agentId).first() # type: Optional[Agent]
  333. if not agent:
  334. logger.error('agent<id={}> is not exist.'.format(dealer.agentId))
  335. return None
  336. # 代理商的成本价. 代理商给经销商设置流量卡费用必须大于等于这个价格
  337. agentAnnualTrafficCost = agent.annualTrafficCost
  338. agentAnnualTrafficCostFen = int((agentAnnualTrafficCost * 100))
  339. primeAgent = agent.primary_agent
  340. name = u'%s 设备号' % subServiceName
  341. items = []
  342. devNo = None
  343. for item in devItems:
  344. if isinstance(item, Device):
  345. dev = item
  346. else:
  347. dev = Device.get_dev(item)
  348. if not dev:
  349. logger.error('{} is not exist.'.format(item))
  350. continue
  351. devNo = dev.devNo
  352. name = '%s %s' % (name, dev.logicalCode)
  353. dev_obj = Device.objects.get(devNo = devNo)
  354. trafficCardCost = dev_obj.trafficCardCost
  355. if not trafficCardCost:
  356. trafficCardCost = getattr(dealer, 'trafficCardCost', None)
  357. if not trafficCardCost:
  358. trafficCardCost = agent.trafficCardCost
  359. platform_cost_fen = RMB.yuan_to_fen(trafficCardCost)
  360. if trafficCardCost > curr_annualTrafficCost:
  361. annualTrafficCost = trafficCardCost
  362. else:
  363. annualTrafficCost = curr_annualTrafficCost
  364. annualTrafficCostFen = RMB.yuan_to_fen(annualTrafficCost)
  365. total_fee += annualTrafficCostFen
  366. group = Group.get_group(dev['groupId'])
  367. # 可以分给代理商和厂商的钱
  368. total_earn_fee = annualTrafficCostFen - platform_cost_fen
  369. # 分给代理商的钱 = 经销商充值费用 - 厂商给代理商配置的成本价格. 如果该值小于0就不分
  370. # 如果该值大于total_earn, 就把total_earn全部分给他
  371. agent_earning_fee = (annualTrafficCostFen - agentAnnualTrafficCostFen) \
  372. if (annualTrafficCostFen - agentAnnualTrafficCostFen) > 0 else 0
  373. if agent_earning_fee > total_earn_fee:
  374. agent_earning_fee = total_earn_fee
  375. manager_earning_fee = total_earn_fee - agent_earning_fee
  376. total_agent_earning_fee += agent_earning_fee
  377. total_manager_earning_fee += manager_earning_fee
  378. item_payload = {
  379. 'name': u'%s %s %s %s' % (dev['devType']['name'], dev['logicalCode'], group['address'], subServiceName),
  380. 'devNo': devNo,
  381. 'iccid': dev.iccid,
  382. 'price': str(annualTrafficCost.amount),
  383. 'dealerPrice': str(curr_annualTrafficCost.amount),
  384. 'cost': str(trafficCardCost.amount),
  385. 'currCost': str(agent.trafficCardCost.amount),
  386. 'agentCost': str(agentAnnualTrafficCost.amount),
  387. 'number': 1,
  388. 'partition': [
  389. {
  390. 'role': ROLE.agent,
  391. 'id': str(agent.id),
  392. 'earned': agent_earning_fee
  393. },
  394. {
  395. 'role': ROLE.manager,
  396. 'id': str(primeAgent.id),
  397. 'earned': manager_earning_fee
  398. }
  399. ]}
  400. items.append(item_payload)
  401. payload = {
  402. 'items': items,
  403. 'name': name,
  404. 'dealerId': str(dealerId),
  405. 'nickname': dealer.nickname,
  406. 'totalFee': 0 if payType == 'manual' else total_fee,
  407. 'settleInfo': {
  408. },
  409. 'wxOrderNo': '',
  410. 'attachParas': {}
  411. }
  412. if str(agent.id) == str(primeAgent.id):
  413. payload['settleInfo']['partition'] = [
  414. {
  415. 'id': str(agent.id),
  416. 'earned': total_agent_earning_fee + total_manager_earning_fee
  417. }
  418. ]
  419. else:
  420. payload['settleInfo']['partition'] = [
  421. {
  422. 'id': str(agent.id),
  423. 'earned': total_agent_earning_fee
  424. },
  425. {
  426. 'id': str(primeAgent.id),
  427. 'earned': total_manager_earning_fee
  428. }
  429. ]
  430. if payType == 'auto':
  431. costMoney = RMB(total_fee / 100.0)
  432. if costMoney > can_used_balance:
  433. raise InsufficientFundsError()
  434. record = DealerRechargeRecord.issue(
  435. sub_type, payment_gateway, dealer, devNo, **payload)
  436. if record:
  437. return record
  438. return None
  439. def create_dealer_sim_charge_verify_order(dealer, partitions, recharge_order):
  440. from apps.web.common.transaction import DealerPaySubType
  441. total_fee = 0
  442. for partition in partitions:
  443. total_fee += partition['earned']
  444. from apps.web.helpers import get_platform_reconcile_pay_gateway
  445. payment_gateway = get_platform_reconcile_pay_gateway()
  446. sub_type = DealerPaySubType.SIM_ORDER_VERIFY
  447. payload = {
  448. 'items': None,
  449. 'name': DealerRechargeRecord.get_product(sub_type)['desc'],
  450. 'dealerId': str(dealer.id),
  451. 'nickname': dealer.nickname,
  452. 'totalFee': total_fee,
  453. 'settleInfo': {'partition': partitions},
  454. 'wxOrderNo': '',
  455. 'attachParas': {'rechargeId': str(recharge_order.id)}
  456. }
  457. record = DealerRechargeRecord.issue(sub_type, payment_gateway, dealer, **payload)
  458. if record:
  459. return record
  460. return None
  461. def create_dealer_charge_order_for_api(dealer, **kw):
  462. from apps.web.common.transaction import DealerPaySubType
  463. from apps.web.core import ROLE
  464. from apps.web.helpers import get_inhourse_wechat_env_pay_gateway
  465. apiDevicePerCost = dealer.api_app.apiDevicePerCost # type: RMB # 单价
  466. subServiceName = DealerRechargeRecord.get_product(DealerPaySubType.API_COST)['desc']
  467. dealerId = dealer.bossId
  468. name = u'%s' % subServiceName
  469. dev_count = kw.get('needQuota', 0)
  470. total_yuan = apiDevicePerCost * Ratio(dev_count)
  471. total_fee = RMB.yuan_to_fen(total_yuan)
  472. payment_gateway = get_inhourse_wechat_env_pay_gateway(
  473. ROLE.dealer) # type: Union[WechatPaymentGateway]
  474. agent = payment_gateway.occupant
  475. items = [
  476. kw
  477. ]
  478. payload = {
  479. 'items': items,
  480. 'name': name,
  481. 'dealerId': str(dealerId),
  482. 'nickname': dealer.nickname,
  483. 'totalFee': total_fee,
  484. 'settleInfo': {
  485. 'partition': [
  486. {
  487. 'id': str(agent.id),
  488. 'earned': total_fee
  489. }
  490. ]
  491. },
  492. 'wxOrderNo': '',
  493. 'attachParas': {}
  494. }
  495. record = DealerRechargeRecord.issue(DealerPaySubType.API_COST,
  496. payment_gateway,
  497. dealer,
  498. **payload)
  499. if record:
  500. return record
  501. return None
  502. def create_dealer_charge_order_for_disable_ad(dealer, devList):
  503. from apps.web.common.transaction import DealerPaySubType
  504. from apps.web.core import ROLE
  505. from apps.web.helpers import get_inhourse_wechat_env_pay_gateway
  506. disableAdCost = dealer.disable_ad_plan.disableAdCost # type: RMB # 单价
  507. cycle = dealer.disable_ad_plan.cycle # type: RMB # 事件周期
  508. subServiceName = DealerRechargeRecord.get_product(DealerPaySubType.DISABLE_AD)['desc']
  509. dealerId = dealer.bossId
  510. payment_gateway = get_inhourse_wechat_env_pay_gateway(
  511. ROLE.dealer) # type: Union[WechatPaymentGateway]
  512. agent = payment_gateway.occupant
  513. dev_count = len(devList)
  514. name = u'添加%s(%s台)' % (subServiceName, dev_count)
  515. total_yuan = disableAdCost * Ratio(dev_count)
  516. total_fee = RMB.yuan_to_fen(total_yuan)
  517. payload = {
  518. 'items': devList,
  519. 'name': name,
  520. 'dealerId': str(dealerId),
  521. 'nickname': dealer.nickname,
  522. 'totalFee': total_fee,
  523. 'settleInfo': {
  524. 'partition': [
  525. {
  526. 'id': str(agent.id),
  527. 'earned': total_fee
  528. }
  529. ]
  530. },
  531. 'wxOrderNo': '',
  532. 'attachParas': {}
  533. }
  534. record = DealerRechargeRecord.issue(DealerPaySubType.DISABLE_AD,
  535. payment_gateway,
  536. dealer,
  537. **payload)
  538. if record:
  539. return record
  540. return None
  541. class DealerSessionBuilder(object):
  542. OPER_ID = 'oper_id'
  543. MASTER_ID = '_auth_user_id'
  544. def __init__(self, request = None):
  545. self.request = request
  546. def is_dealer_trustee(self):
  547. if self.request.session.get(self.OPER_ID):
  548. return True
  549. else:
  550. return False
  551. def check_out_to_dealer_trustee(self, oper_id):
  552. self.request.session[self.OPER_ID] = oper_id
  553. self.request.session.save()
  554. def check_out_to_dealer_master(self):
  555. # 校验master_id
  556. master_id = self.request.session.get(self.MASTER_ID)
  557. dealer = Dealer.objects.filter(id = master_id).first()
  558. subAccouny = Dealer.objects.filter(id = master_id).first()
  559. if not subAccouny and dealer:
  560. self.request.session.clear()
  561. return False
  562. else:
  563. # 切换
  564. self.request.session.pop(self.OPER_ID, None)
  565. self.request.session.save()
  566. return True
  567. class MyToken(object):
  568. KEY = 'c4NEwO'
  569. @staticmethod
  570. def b64encode(j_s):
  571. return base64.urlsafe_b64encode(j_s).replace(b'=', b'')
  572. @staticmethod
  573. def b64decode(b_s):
  574. # 补全签发时替换掉的等号,找规律:肯定能被4整除,替换掉的‘=’个数是:4 - 总长 % 4
  575. rem = len(b_s) % 4
  576. if rem > 0:
  577. b_s += b'=' * (4 - rem)
  578. return base64.urlsafe_b64decode(str(b_s))
  579. @staticmethod
  580. def encode(payload, key = KEY, exp = 60 * 15):
  581. header = {'typ': 'JWT', 'alg': 'HS256'}
  582. header_json = json.dumps(header, sort_keys = True, separators = (',', ':'))
  583. header_bs = MyToken.b64encode(header_json.encode())
  584. my_payload = copy.deepcopy(payload)
  585. my_payload['exp'] = time.time() + int(exp)
  586. payload_json = json.dumps(my_payload, sort_keys = True, separators = (',', ':'))
  587. payload_bs = MyToken.b64encode(payload_json.encode())
  588. if isinstance(key, str):
  589. key = key.encode()
  590. hm = hmac.new(key, header_bs + b'.' + payload_bs, digestmod = hashlib.sha256)
  591. hm_bs = MyToken.b64encode(hm.digest())
  592. return header_bs + b'.' + payload_bs + b'.' + hm_bs
  593. @staticmethod
  594. def decode(jwt_s, key = KEY):
  595. header_bs, payload_bs, sign_bs = jwt_s.split(b'.')
  596. if isinstance(key, str):
  597. key = key.encode()
  598. hm = hmac.new(key, header_bs + b'.' + payload_bs, digestmod = hashlib.sha256)
  599. new_sign_bs = MyToken.b64encode(hm.digest())
  600. if new_sign_bs != sign_bs:
  601. raise
  602. payload_json = MyToken.b64decode(payload_bs)
  603. payload = json.loads(payload_json)
  604. exp = payload['exp']
  605. now_t = time.time()
  606. if now_t > exp:
  607. raise
  608. return payload
  609. class RentOrderServer(object):
  610. def __init__(self, order, dealer=None): # type:(DeviceRentOrder, Optional[Dealer, None]) -> None
  611. self.order = order
  612. self.dealer = dealer or order.dealer
  613. def execute(self):
  614. """
  615. 执行订单的扣款
  616. :return:
  617. """
  618. logger.info("device rent order <{}> start execute!".format(self.order.id))
  619. # 几个常量 扣款目前只从设备收益中进行扣除
  620. incomeType = DEALER_INCOME_TYPE.DEVICE_INCOME
  621. exTime = datetime.datetime.now()
  622. balanceList = self.dealer.__class__.get_income_balance_list(self.dealer, incomeType)
  623. for _sourceKey, _balance in balanceList:
  624. # 金额不足的情况下继续
  625. if RMB(_balance) < RMB(self.order.billAmount):
  626. continue
  627. # 对订单金额进行冻结
  628. update = self.dealer.rent_freeze_balance(
  629. incomeType,
  630. self.order.billAmount,
  631. _sourceKey,
  632. str(self.order.id)
  633. )
  634. if not update:
  635. logger.info("device rent order <{}> execute freeze error!".format(self.order.id))
  636. self.order.update_for_fail(exTime, reason=u"扣款失败")
  637. return False
  638. # 扣款成功的情况下
  639. try:
  640. self.order.update_for_success(exTime, _sourceKey)
  641. except Exception as e:
  642. logger.exception("device rent order <{}> execute update success error = {}!".format(self.order.id, e))
  643. self.dealer.rent_recover_frozen_balance(
  644. incomeType,
  645. self.order.billAmount,
  646. _sourceKey,
  647. str(self.order.id)
  648. )
  649. return False
  650. # 执行成功
  651. self.dealer.rent_clear_frozen_balance(str(self.order.id))
  652. logger.info("device rent order <{}> execute success!".format(self.order.id))
  653. return True
  654. else:
  655. self.order.update_for_fail(exTime, u"余额不足")
  656. # 整个余额都不足够
  657. logger.info("device rent order <{}> dealer <{}> balance not enough".format(self.order.id, self.dealer.id))
  658. return False
  659. class TodoProcessor(object):
  660. @classmethod
  661. def insert_todo(cls, user):
  662. pass
  663. @classmethod
  664. def check_has_done(cls, todo):
  665. return False, False