helpers.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import logging
  4. from functools import wraps, partial
  5. import simplejson as json
  6. from django.http import JsonResponse
  7. from django.utils.module_loading import import_string
  8. from typing import Union, Tuple, TYPE_CHECKING, Optional, Mapping
  9. from apps import serviceCache
  10. from apps.web.constant import APP_TYPE, AppPlatformType
  11. from apps.web.core import ROLE
  12. from apps.web.core.auth.ali import AlipayAuthBridge
  13. from apps.web.core.auth.wechat import WechatAuthBridge
  14. from apps.web.core.bridge import WechatClientProxy
  15. from apps.web.core.datastructures import BaseVisitor
  16. from apps.web.utils import is_user, is_dealer, is_agent, is_anonymous
  17. from apps.web.core.models import WechatPayApp, AliApp, SystemSettings, ManualPayApp
  18. logger = logging.getLogger(__name__)
  19. if TYPE_CHECKING:
  20. from apps.web.device.models import DeviceDict
  21. from apps.web.core.models import WechatManagerApp, WechatAuthApp
  22. from apps.web.ad.models import Advertiser, Advertisement
  23. from apps.web.user.models import MyUser
  24. from apps.web.common.models import WithdrawRecord
  25. from apps.web.dealer.models import SubAccount
  26. from django.contrib.auth.models import AnonymousUser
  27. from apps.web.dealer.models import Dealer
  28. from apps.web.agent.models import Agent
  29. from apps.web.device.models import Device
  30. from apps.web.management.models import Manager
  31. SourceT = Union[Agent, Dealer, Device, Manager, DeviceDict, WithdrawRecord]
  32. from apps.web.core.payment import WechatMiniPaymentGatewayT
  33. WechatAPP_T = Union[WechatPayApp, WechatManagerApp, WechatAuthApp]
  34. class DeviceTypeVisitor(BaseVisitor):
  35. def entry(self, node):
  36. return node.__class__.__name__
  37. def visit_Agent(self, node):
  38. # type:(Agent)->list
  39. """
  40. 主要的逻辑在于,如果该代理商有以自己为准的设备类型则显示,否则则向上查。因为在某些情况下,同一厂商下的代理商可能会与主代理商的部分起冲突
  41. :param node:
  42. :return:
  43. """
  44. from apps.web.device.models import DeviceType
  45. query_set = DeviceType.objects(agentId = str(node.id)).order_by("-popularity")
  46. if query_set.count() > 0:
  47. return query_set
  48. else:
  49. return DeviceType.objects(agentId = str(node.primary_agent.id)).order_by("-popularity")
  50. def visit_Dealer(self, node):
  51. # type:(Dealer)->list
  52. Agent = import_string('apps.web.agent.models.Agent')
  53. agent = Agent.objects(id = node.agentId).get()
  54. return self.visit_Agent(agent)
  55. def visit_SubAccount(self, node):
  56. # type:(SubAccount)->list
  57. Agent = import_string('apps.web.agent.models.Agent')
  58. agent = Agent.objects(id = node.agentId).get()
  59. return self.visit_Agent(agent)
  60. def visit_Manager(self, node):
  61. # type:(Manager)->list
  62. Agent = import_string('apps.web.agent.models.Agent')
  63. agent = Agent.objects(id = str(node.primeAgentId)).get()
  64. return self.visit_Agent(agent)
  65. def visit_Advertiser(self, node):
  66. # type:(Advertiser)->list
  67. from apps.web.management.models import Manager
  68. manager = Manager.objects(id = str(node.managerId)).first()
  69. return self.visit_Manager(manager)
  70. def visit_Advertisement(self, node):
  71. # type:(Advertisement)->list
  72. from apps.web.management.models import Manager
  73. manager = Manager.objects(id = str(node.managerId)).first()
  74. return self.visit_Manager(manager)
  75. class AgentCustomizedBrandVisitor(BaseVisitor):
  76. """
  77. 该访问者主要是为了帮助不同的资源利用者获得
  78. TODO: 减少请求,可以做缓存
  79. keygen := e.g. redis.set(<visitor-name>:<visitor-via(dealer|device|...)>:<hashed-id>, <id-to-retrieve-gateway|bridge>
  80. 目前自定义授权信息和收入流均为代理商所绑定。
  81. 在这里通过不同的对象分发生成相应的网关和桥。
  82. """
  83. def __init__(self, factory_type, **kwargs):
  84. self.model_cls = import_string('apps.web.agent.models.Agent')
  85. self.factory = self.model_cls.factory(factory_type = factory_type, **kwargs)
  86. def entry(self, node):
  87. return node.__class__.__name__
  88. def visit_Agent(self, node):
  89. return self.factory(node)
  90. def visit_Dealer(self, node):
  91. agent = self.model_cls.objects(id = str(node.agentId)).get()
  92. return self.visit_Agent(agent)
  93. def visit_Device(self, node):
  94. Dealer = import_string('apps.web.dealer.models.Dealer')
  95. Agent = import_string('apps.web.agent.models.Agent')
  96. dealer = Dealer.objects(id = node.ownerId).get()
  97. agent = Agent.objects(id = str(dealer.agentId)).get()
  98. return self.visit_Agent(agent)
  99. def visit_Manager(self, node):
  100. Agent = import_string('apps.web.agent.models.Agent')
  101. agent = Agent.objects(id = str(node.primeAgentId)).get()
  102. return self.visit_Agent(agent)
  103. def visit_DeviceDict(self, node):
  104. # type:(DeviceDict)->None
  105. """默认视为deviceDict"""
  106. Dealer = import_string('apps.web.dealer.models.Dealer')
  107. Agent = import_string('apps.web.agent.models.Agent')
  108. dealer = Dealer.objects(id = str(node['ownerId'])).get()
  109. agent = Agent.objects(id = str(dealer.agentId)).get()
  110. return self.visit_Agent(agent)
  111. def visit_WithdrawRecord(self, node):
  112. # type:(WithdrawRecord)->None
  113. Dealer = import_string('apps.web.dealer.models.Dealer')
  114. Agent = import_string('apps.web.agent.models.Agent')
  115. if node.role == ROLE.dealer:
  116. dealer = Dealer.objects(id = node.ownerId).get()
  117. agent = Agent.objects(id = str(dealer.agentId)).get()
  118. elif node.role == ROLE.agent:
  119. agent = Agent.objects(id = str(node.ownerId)).get()
  120. else:
  121. assert False, 'invalid withdraw record role'
  122. return self.visit_Agent(agent)
  123. def visit_dict(self, node):
  124. return self.visit_DeviceDict(node)
  125. class DealerCustomizedBrandVisitor(BaseVisitor):
  126. def __init__(self, factory_type, **kwargs):
  127. self.model_cls = import_string('apps.web.dealer.models.Dealer')
  128. self.factory = self.model_cls.factory(factory_type = factory_type, **kwargs)
  129. def entry(self, node):
  130. return node.__class__.__name__
  131. def visit_Dealer(self, node):
  132. return self.factory(node)
  133. def is_same_wechat_app(leftApp, rightApp):
  134. # type:(WechatAPP_T, WechatAPP_T)->bool
  135. if leftApp['appid'] == rightApp['appid']:
  136. return True
  137. else:
  138. return False
  139. def get_app(source, app_type, vistor = AgentCustomizedBrandVisitor, **kwargs):
  140. # type:(SourceT, str, Optional[AgentCustomizedBrandVisitor, DealerCustomizedBrandVisitor], Mapping[str, str])->WechatAPP_T
  141. logger.debug('get wechat app. source = {}; app type = {}; vistor = {}; kwargs = {}'.format(
  142. repr(source), app_type, vistor.__name__, str(kwargs)))
  143. return vistor(factory_type = 'app', app_type = app_type, **kwargs).visit(source)
  144. def get_user_manager_agent(source):
  145. # type:(SourceT)->Agent
  146. app = get_app(source = source, app_type = APP_TYPE.WECHAT_USER_MANAGER)
  147. return getattr(app, 'occupant')
  148. def get_ali_auth_bridge(source, app_type):
  149. # type:(SourceT)->AlipayAuthBridge
  150. app = get_app(source, app_type)
  151. return AlipayAuthBridge(app)
  152. def get_wechat_auth_bridge(source, app_type):
  153. # type:(SourceT, str)->WechatAuthBridge
  154. app = get_app(source, app_type)
  155. return WechatAuthBridge(app = app)
  156. def get_wechat_user_messager_app(source):
  157. return get_app(source, APP_TYPE.WECHAT_USER_MESSAGER)
  158. def get_wechat_user_manager_mp_proxy(source):
  159. app = get_app(source, APP_TYPE.WECHAT_USER_MANAGER)
  160. return WechatClientProxy(app)
  161. def get_wechat_user_sub_manager_mp_proxy(source):
  162. app = get_app(source, APP_TYPE.WECHAT_USER_SUBSCRIBE_MANAGER)
  163. return WechatClientProxy(app)
  164. def get_wechat_dealer_sub_manager_mp_proxy(source):
  165. app = get_app(source, APP_TYPE.WECHAT_DEALER_SUBSCRIBE_MANAGER)
  166. return WechatClientProxy(app)
  167. def get_wechat_manager_mp_proxy(source):
  168. app = get_app(source, APP_TYPE.WECHAT_MANAGER)
  169. return WechatClientProxy(app)
  170. def get_inhouse_wechat_manager_mp_proxy():
  171. Agent = import_string('apps.web.agent.models.Agent')
  172. app = Agent.get_platform_wechat_manager_app()
  173. return WechatClientProxy(app)
  174. def get_inhouse_wechat_user_manager_mp_proxy():
  175. Agent = import_string('apps.web.agent.models.Agent')
  176. app = Agent.get_inhouse_wechat_user_manager_app()
  177. return WechatClientProxy(app)
  178. def get_wechat_mini_env_pay_gateway(source, role = None, pay_app_type = None):
  179. # type:(SourceT, str, str)->WechatMiniPaymentGatewayT
  180. app = get_app(source = source,
  181. app_type = APP_TYPE.WECHAT_MINI_ENV_PAY,
  182. role = role,
  183. pay_app_type = pay_app_type) # type: WechatAPP_T
  184. return app.new_gateway(AppPlatformType.WECHAT_MINI)
  185. def get_wechat_env_pay_gateway(source, role = None, pay_app_type = None, vistor = AgentCustomizedBrandVisitor):
  186. app = get_app(source = source,
  187. app_type = APP_TYPE.WECHAT_ENV_PAY,
  188. role = role,
  189. vistor = vistor,
  190. pay_app_type = pay_app_type) # type: Optional[WechatPayApp]
  191. return app.new_gateway(AppPlatformType.WECHAT)
  192. def get_alipay_env_pay_gateway(source, role = None, pay_app_type = None, vistor = AgentCustomizedBrandVisitor):
  193. app = get_app(source = source,
  194. app_type = APP_TYPE.ALIPAY_ENV_PAY,
  195. role = role,
  196. vistor = vistor,
  197. pay_app_type = pay_app_type) # type: Optional[AliApp]
  198. return app.new_gateway(AppPlatformType.ALIPAY)
  199. def get_inhourse_wechat_env_pay_gateway(role = None, pay_app_type = None):
  200. """
  201. 使用平台商户进行支付. 例如SIM卡充值, API充值以及清静计划充值等
  202. 都在在微信环境在进行支付
  203. :param role:
  204. :param pay_app_type:
  205. :return:
  206. """
  207. Agent = import_string('apps.web.agent.models.Agent')
  208. inhouse_agent = Agent.get_inhouse_prime_agent() # type: Agent
  209. app = get_app(source = inhouse_agent,
  210. app_type = APP_TYPE.WECHAT_ENV_PAY,
  211. role = role,
  212. pay_app_type = pay_app_type) # type: Optional[WechatPayApp]
  213. return app.new_gateway(AppPlatformType.WECHAT)
  214. def get_platform_promotion_pay_gateway(pay_app_type):
  215. """
  216. 平台推广支付网关. 包括红包支付等, 提现广告记录在平台
  217. :param pay_app_type:
  218. :return:
  219. """
  220. from apps.web.core.models import PlatformPromotionApp
  221. return PlatformPromotionApp.get_app().new_gateway(pay_app_type)
  222. def get_platform_wallet_pay_gateway(pay_app_type):
  223. """
  224. 平台钱包支持. SIM卡自动充值, 代理商分成提现记录在平台
  225. :param pay_app_type:
  226. :return:
  227. """
  228. from apps.web.core.models import PlatformWalletApp
  229. return PlatformWalletApp.get_app().new_gateway(pay_app_type)
  230. def get_platform_reconcile_pay_gateway():
  231. """
  232. 平台对账支付网关. 用于账务错误给客户分账
  233. :return:
  234. """
  235. from apps.web.core.models import PlatformPromotionApp
  236. return PlatformPromotionApp.get_app().new_gateway(AppPlatformType.PLATFORM)
  237. def get_swap_env_pay_gateway(source):
  238. app = get_app(source = source,
  239. app_type = APP_TYPE.SWAP_ENV_PAY,
  240. vistor = DealerCustomizedBrandVisitor) # type: Optional[SwapPayApp]
  241. return app.new_gateway(AppPlatformType.SWAP)
  242. def get_manual_pay_gateway(dealer):
  243. my_app = ManualPayApp.get_null_app()
  244. my_app.occupantId = str(dealer.id)
  245. my_app.occupant = dealer
  246. return my_app.new_gateway(AppPlatformType.PLATFORM)
  247. #############
  248. ### Misc ####
  249. #############
  250. def get_wx_config(user, url):
  251. # type:(Union[Dealer, Agent, MyUser, AnonymousUser], str)->Tuple[int, dict]
  252. try:
  253. if not user or is_anonymous(user):
  254. wechat_mp_proxy = get_inhouse_wechat_manager_mp_proxy()
  255. elif is_user(user):
  256. agent_id = str(user.agentId)
  257. Agent = import_string('apps.web.agent.models.Agent')
  258. agent = Agent.objects(id = agent_id).get()
  259. wechat_mp_proxy = get_wechat_user_manager_mp_proxy(agent)
  260. elif is_dealer(user) or is_agent(user):
  261. wechat_mp_proxy = get_wechat_manager_mp_proxy(user)
  262. else:
  263. wechat_mp_proxy = None
  264. if wechat_mp_proxy:
  265. value = wechat_mp_proxy.generate_js_auth_signature(url = url)
  266. logger.info('get wx config success. wxconfig = %s; proxy = %s' % (json.dumps(value), repr(wechat_mp_proxy)))
  267. else:
  268. value = {}
  269. logger.info('get wx config failure. auth bridge is null. user = %s' % repr(user))
  270. except Exception as e:
  271. value = {}
  272. logger.exception('get wx config failure. error = %s; user = %s' % (e, repr(user)))
  273. return value
  274. def current_platform(gateway_key, current):
  275. if gateway_key == current:
  276. return u'新平台'
  277. else:
  278. return u'老平台'
  279. DEVICE_LOCK_KEY = '{item}-operation-lock'
  280. def device_lock_key(item):
  281. # type:(str)->str
  282. return DEVICE_LOCK_KEY.format(item = item)
  283. START_DEVICE_LOCK_KEY = '{openId}-start-device-lock'
  284. def start_device_lock_key(openId):
  285. return START_DEVICE_LOCK_KEY.format(openId=openId)
  286. class ServerSwitch(object):
  287. # 拦截状态
  288. payment_blocking_status = ['pass', 'blocking', 'alipay', 'wechat']
  289. payment_Type = []
  290. white_list = ['o-VzzwAfpdglJY38Kj7yMvVWlIgw']
  291. @staticmethod
  292. def payment_switch(responseText):
  293. def warpper(func):
  294. @wraps(func)
  295. def inner(request, *args, **kwargs):
  296. status = ServerSwitch.check_payment_blocking_status(request)
  297. if status == True:
  298. logger.info('Blocking function:{}.{}()'.format(inner.__module__, inner.__name__))
  299. return JsonResponse({'result': 0, 'description': responseText, 'payload': {}})
  300. return func(request, *args, **kwargs)
  301. return inner
  302. return warpper
  303. @classmethod
  304. def check_payment_blocking_status(cls, request):
  305. result = False
  306. # 白名单获取
  307. white_list = serviceCache.get('adminWhiteList')
  308. if not white_list:
  309. white_list_obj = SystemSettings.objects.filter(key = 'adminWhiteList').first()
  310. if not white_list_obj:
  311. white_list = []
  312. serviceCache.set('adminWhiteList', white_list)
  313. else:
  314. white_list = white_list_obj.value
  315. serviceCache.set('adminWhiteList', white_list)
  316. if not serviceCache.get('paymentSwitch'):
  317. sys_setting = SystemSettings.objects.filter(key = 'paymentSwitch').first()
  318. if not sys_setting:
  319. logger.info('Can not get server settings,please touch mongodb')
  320. else:
  321. # 白名单(openid)
  322. if str(request.user.openId) in white_list:
  323. logger.info('user in white list user={}'.format(str(request.user.openId)))
  324. result = False
  325. # 全部拦截
  326. elif sys_setting.value == 'blocking':
  327. serviceCache.set('paymentSwitch', sys_setting.value)
  328. result = True
  329. # 拦截微信
  330. elif sys_setting.value == 'wechat':
  331. user_agent = request.META.get('HTTP_USER_AGENT')
  332. if 'MicroMessenger' in user_agent:
  333. serviceCache.set('paymentSwitch', sys_setting.value)
  334. result = True
  335. logger.info('Blocking wechat user request')
  336. # 拦截支付宝
  337. elif sys_setting.value == 'alipay':
  338. user_agent = request.META.get('HTTP_USER_AGENT')
  339. if 'Alipay' in user_agent:
  340. serviceCache.set('paymentSwitch', sys_setting.value)
  341. result = True
  342. logger.info('Blocking alipay user request')
  343. else:
  344. # 白名单(openid)
  345. if str(request.user.openId) in white_list:
  346. logger.info('user in white list user={}'.format(str(request.user.openId)))
  347. result = False
  348. elif serviceCache.get('paymentSwitch') == 'blocking':
  349. result = True
  350. elif serviceCache.get('paymentSwitch') == 'wechat':
  351. user_agent = request.META.get('HTTP_USER_AGENT')
  352. if 'MicroMessenger' in user_agent:
  353. result = True
  354. logger.info('Blocking wechat user request')
  355. elif serviceCache.get('paymentSwitch') == 'alipay':
  356. user_agent = request.META.get('HTTP_USER_AGENT')
  357. if 'Alipay' in user_agent:
  358. result = True
  359. logger.info('Blocking alipay user request')
  360. return result
  361. # 设置支付拦截状态
  362. @classmethod
  363. def set_payment_blocking_switch(cls, status = None):
  364. """
  365. status='pass':不拦截
  366. status='blocking':全部拦截
  367. status='wechat':拦截微信
  368. status='alipay':拦截支付宝
  369. """
  370. payment_blocking_status = ['pass', 'blocking', 'alipay', 'wechat']
  371. sys_setting = SystemSettings.objects.filter(key = 'paymentSwitch').first()
  372. if not sys_setting:
  373. logger.info('Can not get server settings,please touch mongodb')
  374. else:
  375. try:
  376. if not status:
  377. sys_setting.value = 'pass'
  378. sys_setting.save()
  379. serviceCache.delete('paymentSwitch')
  380. serviceCache.set('paymentSwitch', 'pass')
  381. logger.info('Set Payment blocking switch is fail ,status : pass')
  382. elif status not in payment_blocking_status:
  383. logger.info('That is not an option , status : ["pass", "blocking", "alipay", "wechat"]')
  384. else:
  385. sys_setting.value = status
  386. sys_setting.save()
  387. serviceCache.delete('paymentSwitch')
  388. serviceCache.set('paymentSwitch', status)
  389. logger.info('Payment blocking is running ,status : {}'.format(status))
  390. except Exception:
  391. logger.error('Set payment blocking switch is fail, try edit in mongodb')
  392. # 添加删除修改白名单
  393. @classmethod
  394. def edit_payment_while_list(cls, editType = 'add', openIds = []):
  395. white_list_obj = SystemSettings.objects.filter(key = 'adminWhiteList').first() or SystemSettings(
  396. key = 'adminWhiteList', value = [])
  397. white_list = white_list_obj.value
  398. if editType == 'add':
  399. white_list = white_list + openIds
  400. white_list_obj.value = list(set(white_list))
  401. white_list_obj.save()
  402. elif editType == 'update':
  403. white_list = openIds
  404. white_list_obj.value = list(set(white_list))
  405. white_list_obj.save()
  406. elif editType == 'delete':
  407. white_list = set(white_list) - set(openIds)
  408. white_list_obj.value = list(white_list)
  409. white_list_obj.save()
  410. if editType == 'clear':
  411. white_list_obj.delete()
  412. serviceCache.delete('adminWhiteList')
  413. serviceCache.set('adminWhiteList', white_list)
  414. def detect_browser(self, request, browser_name):
  415. user_agent = request.META.get('HTTP_USER_AGENT', 'unknown')
  416. return browser_name in user_agent
  417. detect_alipay_client = partial(detect_browser, browser_name = 'Alipay')
  418. detect_wechat_client = partial(detect_browser, browser_name = 'MicroMessenger')
  419. detect_union_client = partial(detect_browser, browser_name = 'Union')
  420. detect_jdpay_client = partial(detect_browser, browser_name = 'jdapp')
  421. detect_jdjr_client = partial(detect_browser, browser_name = 'JDJR')
  422. def remove_some_desc_for_consume(oldDesc, needRemove):
  423. vList = oldDesc.split(' ')
  424. try:
  425. sIndex = vList.index(needRemove)
  426. except Exception, e:
  427. return oldDesc
  428. vList[sIndex], vList[sIndex + 1], vList[sIndex + 2] = '', '', ''
  429. return ' '.join(vList)