utils.py 31 KB

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