models.py 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. """
  4. web.agent.models
  5. ~~~~~~~~~
  6. """
  7. import simplejson as json
  8. import datetime
  9. import itertools
  10. import logging
  11. import urllib
  12. from bson.objectid import ObjectId
  13. from django.conf import settings
  14. from django.utils.module_loading import import_string
  15. from mongoengine import MapField, LazyReferenceField, IntField
  16. from mongoengine.errors import DoesNotExist
  17. from mongoengine.fields import (StringField, DictField, BooleanField, DateTimeField, EmbeddedDocumentField,
  18. ListField, ReferenceField)
  19. from typing import Any, Dict, TYPE_CHECKING, Optional, cast
  20. from collections import namedtuple
  21. from apilib.monetary import RMB, sum_rmb, Permillage, Percent
  22. from apps.web.agent.define import AgentConst, AGENT_INCOME_SOURCE, AGENT_INCOME_TYPE
  23. from apps.web.agent.errors import PrimaryAgentDoesNotExist
  24. from apps.web.common.models import WithdrawRecord, CapitalUser, Balance, WithdrawBankCard
  25. from apps.web.constant import Const, AppPlatformType, DEALER_CONSUMPTION_AGG_KIND, MoniAppStatus
  26. from apps.web.core.db import Searchable, MonetaryField, StrictDictField, PermillageField, PercentField
  27. from apps.web.core.exceptions import InvalidParameter, NoAgentFound, NoManagerFound, MerchantError
  28. from apps.web.core.messages.sms import agentWithdrawSMSProvider
  29. from apps.web.core.models import WechatManagerApp, WechatAuthApp, BoundOpenInfo, AliApp, WechatPayApp, \
  30. WechatMiniApp, WechatUserManagerApp, BankCard, \
  31. WithdrawEntity, WechatUserSubscribeManagerApp, WechatDealerSubscribeManagerApp
  32. from apps.web.core import PayAppType, APP_KEY_DELIMITER, ROLE
  33. from apps.web.core.payment import WithdrawGateway
  34. from apps.web.device.models import DeviceType
  35. from apps.web.management.models import Manager
  36. from apps.web.common.transaction import WITHDRAW_PAY_TYPE
  37. from apps.web.exceptions import UserServerException
  38. logger = logging.getLogger(__name__)
  39. if TYPE_CHECKING:
  40. from pymongo.results import UpdateResult
  41. from apps.web.common.transaction import WithdrawHandler
  42. from apps.web.core import PayAppBase
  43. AgentDisclaimer = namedtuple("AgentDisclaimer", ["content", "version"])
  44. class Agent(CapitalUser):
  45. """
  46. 代理商, 管理经销商
  47. """
  48. INCOME_SOURCE_LIST = AGENT_INCOME_SOURCE.choices()
  49. INCOME_SOURCE_TO_TYPE = AgentConst.MAP_SOURCE_TO_TYPE
  50. INCOME_TYPE_LIST = AGENT_INCOME_TYPE.choices()
  51. INCOME_TYPE_TO_FIELD = AgentConst.MAP_TYPE_TO_FIELD
  52. #: 默认的收入频道的分布情况
  53. DEFAULT_INCOME_MAP = {
  54. AGENT_INCOME_SOURCE.AD: 0,
  55. AGENT_INCOME_SOURCE.DEALER_WITHDRAW_FEE: 0,
  56. AGENT_INCOME_SOURCE.DEALER_CARD_FEE: 0,
  57. AGENT_INCOME_SOURCE.DEALER_DEVICE_FEE: 0
  58. }
  59. #: 默认管理平台微信公众号授权用户信息
  60. DEFAULT_WECHAT_AUTH_USER_INFO = {
  61. 'avatar': '',
  62. 'sex': 0
  63. }
  64. DEFAULT_PAY_TYPE = {
  65. ROLE.agent: {
  66. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  67. AppPlatformType.WECHAT: PayAppType.WECHAT,
  68. },
  69. ROLE.dealer: {
  70. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  71. AppPlatformType.WECHAT: PayAppType.WECHAT,
  72. },
  73. ROLE.myuser: {
  74. AppPlatformType.ALIPAY: PayAppType.ALIPAY,
  75. AppPlatformType.WECHAT: PayAppType.WECHAT,
  76. AppPlatformType.WECHAT_MINI: PayAppType.WECHAT_MINI
  77. }
  78. }
  79. DEFAULT_CHECKPOINT = {
  80. 'gerenzhongxin': False, 'yue': False, 'baogaolaoban': False, 'fukuan': False
  81. }
  82. #: 收入相关
  83. deviceBalance = MapField(field = EmbeddedDocumentField(Balance))
  84. trafficBalance = MapField(field = EmbeddedDocumentField(Balance))
  85. adBalance = MapField(field = EmbeddedDocumentField(Balance))
  86. withdrawBalance = MapField(field = EmbeddedDocumentField(Balance))
  87. insuranceBalance = MapField(field = EmbeddedDocumentField(Balance))
  88. apiQuotaBalance = MapField(field = EmbeddedDocumentField(Balance))
  89. disableAdBalance = MapField(field = EmbeddedDocumentField(Balance))
  90. incomeMap = DictField(verbose_name = '收入字典', default = DEFAULT_INCOME_MAP)
  91. aggregatedIncome = DictField(verbose_name = '累计收入,只增不减', default = DEFAULT_INCOME_MAP)
  92. #: 代理自定义自己的产品名称和logo
  93. productLogo = StringField(verbose_name = "产品logo", default = "")
  94. productName = StringField(verbose_name = "产品名称", default = "")
  95. #: 公众号相关
  96. gzhServiceQrcodeUrl = StringField(verbose_name = "公众号二维码", default = "", max_length = 256)
  97. gzhServiceLinkUrl = StringField(verbose_name = "公众号链接二维码", default = "")
  98. forceFollowGzh = BooleanField(verbose_name = "是否强制关注公众号", default = False)
  99. forceFollowGzhForDealer = StringField(verbose_name = u'经销商的强制关注开关',
  100. default = 'free') # never:此代理商下的经销商,永远不准强制关注,free:经销商自由允许关注
  101. title = StringField(verbose_name = u'公众号强制关注的时候,显示的title', default = u'为了充分保障您的支付权益,首次使用需要您关注服务公众号辅助使用设备。')
  102. desc = StringField(verbose_name = u'公众号的加粉描述', default = u'关注步骤:手指按在上面二维码上,弹出窗口后,点公众号关注后您再次扫描设备上二维码启动设备。')
  103. #: 用于客服的联系方式
  104. serviceName = StringField(verbose_name = "客服名称", default = "", max_length = 32)
  105. servicePhone = StringField(verbose_name = "客服电话", default = "", max_length = 32)
  106. serviceQrcodeUrl = StringField(verbose_name = "二维码图片链接", default = "", max_length = 256)
  107. #: 特性列表
  108. features = ListField(field = StringField(), verbose_name = u'支持的特性', default = [])
  109. customizedUserGzhAllowable = BooleanField(verbose_name = '用户自定义公众号', default = False)
  110. customizedUserSubGzhAllowable = BooleanField(verbose_name = '用户订阅通知自定义公众号', default = False)
  111. customizedDealerGzhAllowable = BooleanField(verbose_name = '经销商自定义公众号', default = False)
  112. customizedDealerSubGzhAllowable = BooleanField(verbose_name = '经销商订阅通知自定义公众号', default = False)
  113. managerId = StringField(verbose_name = "从属于哪个管理员", null = False)
  114. adShow = BooleanField(verbose_name = "广告收入显示与否的选项", default = False)
  115. # 厂商参与设备运营分成的商户比例 资金池模式
  116. managerProfitShare = PercentField(verbose_name='代理商设备运营分成比例', default = Percent('0.00'))
  117. # 厂商参与设备运营分成的商户比例 暂不支持,但是可以先预留
  118. managerMerProfitShare = PercentField(verbose_name='代理商设备运营分成比例 商户收款', default = Percent('0.00'))
  119. # 提现OPENID映射
  120. payOpenIdMap = MapField(EmbeddedDocumentField(BoundOpenInfo))
  121. #: 用于API接口
  122. openAPI = BooleanField(verbose_name = "是否支持openAPI", default = False)
  123. agentSign = StringField(verbose_name = '唯一签名用于API调用,agent的签名,用于核对对方的签名')
  124. mySign = StringField(verbose_name = '唯一签名用于API调用,我们的签名,用于请求对方URl,对方鉴权')
  125. domain = StringField(verbose_name = '域名调用URL')
  126. wechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '授权APP', document_type = WechatAuthApp)
  127. oldWechatLoginAuthApp = EmbeddedDocumentField(verbose_name = '老的授权APP',
  128. document_type = WechatAuthApp, default = None)
  129. wechatUserManagerialApp = EmbeddedDocumentField(verbose_name = '用户管理APP', document_type = WechatUserManagerApp)
  130. wechatDealerManagerialApp = EmbeddedDocumentField(verbose_name = '经销商管理APP', document_type = WechatManagerApp)
  131. wechatUserSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '用户订阅通知APP',
  132. document_type = WechatUserSubscribeManagerApp)
  133. wechatDealerSubscribeManagerApp = EmbeddedDocumentField(verbose_name = '经销商订阅通知APP',
  134. document_type = WechatDealerSubscribeManagerApp)
  135. # 支付APP配置
  136. customizedAlipayCashflowAllowable = BooleanField(verbose_name = '是否开启自主支付宝收款权限', default = False)
  137. payAppAli = LazyReferenceField(document_type = AliApp, default = None)
  138. customizedWechatCashflowAllowable = BooleanField(verbose_name = '是否开启微信自主收款权限', default = False)
  139. payAppWechat = LazyReferenceField(document_type = WechatPayApp, verbose_name = '微信支付APP(用于提现和支付)', default = None)
  140. featureToggles = DictField(verbose_name = '特性开关', default = {})
  141. # 厂商给代理商的流量卡成本价. 提交经销商的时候,经销商的默认价格从这个继承,代理商也可以更改
  142. annualTrafficCost = MonetaryField(verbose_name = '厂商给代理商的流量卡成本价', default = Const.PLATFORM_DEFAULT_TRAFFIC_COST)
  143. # 平台给厂商的流量卡成本. 添加厂商的时候设置, 添加代理商的时候从厂商配置继承.
  144. # 如果有更改,更改之前的代理商不会修改这个费用,如果修改需要直接修改
  145. trafficCardCost = MonetaryField(verbose_name = '平台给厂商的流量卡成本价', default = Const.PLATFORM_DEFAULT_TRAFFIC_COST)
  146. # 厂商给代理商的提现费率. 代理商给经销商设置的提现费率必须大于该值. 配置资金池的代理商不受限制
  147. withdrawFeeRatio = PermillageField(verbose_name = '厂商给代理商的提现费率下限',
  148. default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO,
  149. min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO,
  150. max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO)
  151. # 平台给厂商的提现费率. 厂商给代理商的提现费率下限必须大于该值. 这个是平台成本价.
  152. withdrawFeeRatioCost = PermillageField(verbose_name = '平台给厂商的提现费率',
  153. default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO,
  154. min_value = Const.MIN_DEALER_WITHDRAW_FEE_RATIO,
  155. max_value = Const.MAX_DEALER_WITHDRAW_FEE_RATIO)
  156. bannerList = ListField(field = DictField(), verbose_name = "banner列表", default = [])
  157. #: 大部分情况下,是不允许直接变更支付网关的
  158. #: 手工给其转账的时候把该开关打开
  159. isChangingPaymentGateway = BooleanField(default = False)
  160. payType = DictField(verbose_name = u'各应用支付方式', default = DEFAULT_PAY_TYPE)
  161. moniAppList = ListField(verbose_name = u'监督公众号的清单', default = [])
  162. moniAppCheckPointDict = DictField(verbose_name = u'是否需要弹出监督公众号的代理商检查点', default = DEFAULT_CHECKPOINT)
  163. maxPayLimit = IntField(verbose_name = u'最大充值金额限制', default = 500)
  164. cardWechatInfo = DictField(verbose_name = u'批量导入实体卡绑定内勤微信账号信息', default = {})
  165. boundCardName = StringField(verbose_name = u'默认绑定卡主姓名', default = '')
  166. boundCardPhone = StringField(verbose_name = u'默认绑定卡主手机号', default = '')
  167. withdrawApps = MapField(EmbeddedDocumentField(document_type = WithdrawEntity), default = None)
  168. # 寻找 免责声明的 流程是 代理商--->主代理商--->系统代理商
  169. needDisclaimer = BooleanField(verbose_name = u"代理商是否需要免责声明", default = True)
  170. disclaimer = StringField(verbose_name = u"代理商设置的用户的免责声明", default = "")
  171. disclaimer_version = StringField(verbose_name = u"当前免责声明的版本,声明更新的时候须将版本号更新", default = "v1.0.0")
  172. dealerBankWithdrawFee = BooleanField(verbose_name = u'经销商提现到银行卡的手续费,计算方式', default = False)
  173. # 以下字段不在支持
  174. bankcards = ListField(ReferenceField(BankCard), default = [])
  175. meta = {
  176. 'indexes': [
  177. {
  178. 'fields': ['username'],
  179. 'unique': True
  180. }
  181. ],
  182. "collection": "Agent",
  183. "db_alias": "default"
  184. }
  185. search_fields = ('username', 'nickname', 'openId', 'remarks')
  186. def __str__(self):
  187. return 'Agent<id={} username={} nickname={}>'.format(str(self.id), self.username, self.nickname)
  188. @property
  189. def service_phone(self):
  190. return self.servicePhone
  191. def my_pay_type(self, role, gateway_type):
  192. if role in self.payType and gateway_type in self.payType[role]:
  193. return self.payType[role][gateway_type]
  194. else:
  195. return self.DEFAULT_PAY_TYPE[role][gateway_type]
  196. @property
  197. def my_wechat_pay_app(self):
  198. # type: ()->WechatPayApp
  199. if self.customizedWechatCashflowAllowable:
  200. if not self.payAppWechat:
  201. raise Exception(u'第三方支付配置错误(1001)')
  202. else:
  203. my_app = self.payAppWechat.fetch() # type: WechatPayApp
  204. if not my_app.valid:
  205. raise Exception(u'第三方支付配置错误(1002)')
  206. else:
  207. if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse):
  208. raise Exception(u'第三方支付配置错误(1003)')
  209. my_app.occupantId = str(self.id)
  210. my_app.occupant = self
  211. return my_app
  212. my_app = WechatPayApp.get_null_app()
  213. my_app.occupantId = str(self.id)
  214. my_app.occupant = self
  215. return my_app
  216. @property
  217. def my_ali_pay_app(self):
  218. # type: ()->AliApp
  219. if self.customizedAlipayCashflowAllowable:
  220. if not self.payAppAli:
  221. raise Exception(u'第三方支付配置错误(1001)')
  222. else:
  223. my_app = self.payAppAli.fetch() # type: AliApp
  224. if not my_app.valid:
  225. raise Exception(u'第三方支付配置错误(1002)')
  226. else:
  227. if (str(self.id) == settings.MY_PRIMARY_AGENT_ID) and (not my_app.inhouse):
  228. raise Exception(u'第三方支付配置错误(1003)')
  229. my_app.occupantId = str(self.id)
  230. my_app.occupant = self
  231. return my_app
  232. my_app = AliApp.get_null_app()
  233. my_app.occupantId = str(self.id)
  234. my_app.occupant = self
  235. return my_app
  236. def to_dict(self, shadow = False):
  237. rv = super(Agent, self).to_dict()
  238. rv.update({
  239. 'id': str(self.id),
  240. 'productLogo': self.product_logo,
  241. 'productName': self.product_name,
  242. 'serviceName': self.serviceName,
  243. 'servicePhone': self.servicePhone,
  244. 'serviceQrcodeUrl': self.serviceQrcodeUrl,
  245. 'gzhServiceQrcodeUrl': self.gzhServiceQrcodeUrl,
  246. 'adShow': self.adShow,
  247. 'forceFollowGzh': self.forceFollowGzh,
  248. 'managerId': self.managerId,
  249. 'isPrimary': self.is_primary,
  250. 'featureList': self.feature_list,
  251. 'annualTrafficCost': self.annualTrafficCost,
  252. 'trafficCardCost': self.trafficCardCost,
  253. 'title': self.title,
  254. 'desc': self.desc,
  255. 'smsVendor': self.smsVendor,
  256. 'dealerBankWithdrawFee': self.dealerBankWithdrawFee,
  257. 'bankWithdrawFee': self.bankWithdrawFee
  258. })
  259. return rv
  260. @classmethod
  261. def filter(cls, manager_id, search_key, page_index, page_size, moniAppId = None, shadow = False):
  262. query = {'managerId': manager_id} if manager_id is not None else {}
  263. if moniAppId:
  264. query.update({'moniAppList': moniAppId})
  265. agents = cls.objects(**query).search(search_key).order_by('-dateTimeAdded')
  266. total = agents.count()
  267. dataList = []
  268. for agent in agents.paginate(pageIndex = page_index, pageSize = page_size): # type: Agent
  269. deviceTypes = DeviceType.objects(agentId = str(agent.id)).all()
  270. if len(deviceTypes):
  271. devTypeList = [_.to_dict() for _ in deviceTypes]
  272. else:
  273. devTypeList = []
  274. if Manager.objects(primeAgentId = str(agent.id)).first():
  275. isPrimary = True
  276. else:
  277. isPrimary = False
  278. zhejiangDict = {}
  279. try:
  280. from apps.web.south_intf.zhejiang_fire import ZhejiangNorther
  281. obj = ZhejiangNorther.objects.get(tokenId = str(agent.id))
  282. ip, port = obj.northPort.split(":")
  283. zhejiangDict = {
  284. 'loginUsername': obj.usernameFromHear,
  285. 'loginPassword': obj.passwordFromHear,
  286. 'mqUsername': obj.mqUser,
  287. 'mqPassword': obj.mqPassword,
  288. 'code': obj.serviceCodeFromNorth,
  289. "northIp": ip,
  290. "northPort": port
  291. }
  292. except Exception:
  293. pass
  294. item = {
  295. 'id': str(agent.id),
  296. 'nickname': agent.nickname,
  297. 'username': '******' if shadow else agent.username,
  298. 'annualTrafficCost': agent.annualTrafficCost,
  299. 'trafficCardCost': agent.trafficCardCost,
  300. 'withdrawFeeRatio': float(agent.withdrawFeeRatio.amount),
  301. 'withdrawFeeRatioCost': float(agent.withdrawFeeRatioCost.amount),
  302. 'managerProfitShare': float(agent.managerProfitShare.amount),
  303. 'dealerTotal': agent.get_dealers().count(),
  304. 'dateTimeAdded': agent.dateTimeAdded,
  305. 'featureList': agent.feature_list,
  306. 'bannerImgList': agent.bannerList,
  307. 'customizedAlipayCashflowAllowable': agent.customizedAlipayCashflowAllowable,
  308. 'customizedWechatCashflowAllowable': agent.customizedWechatCashflowAllowable,
  309. 'customizedDealerGzhAllowable': agent.customizedDealerGzhAllowable,
  310. 'customizedDealerSubGzhAllowable': agent.customizedDealerSubGzhAllowable,
  311. 'customizedUserGzhAllowable': agent.customizedUserGzhAllowable,
  312. 'customizedUserSubGzhAllowable': agent.customizedUserSubGzhAllowable,
  313. 'isPrimary': isPrimary,
  314. 'deviceType': devTypeList,
  315. 'detail': {
  316. 'agentId': str(agent.id),
  317. 'managerId': agent.managerId,
  318. 'features': json.dumps(agent.feature_boolean_map)
  319. },
  320. 'ZJFirePlatform': zhejiangDict,
  321. 'forceFollowGzh': 'yes' if agent.forceFollowGzh else 'no',
  322. 'forceFollowGzhForDealer': agent.forceFollowGzhForDealer,
  323. 'productName': agent.productName,
  324. 'maxPayLimit': agent.maxPayLimit
  325. }
  326. try:
  327. pay_app_ali = agent.my_ali_pay_app # type: Optional[AliApp]
  328. except Exception as e:
  329. pay_app_ali = None
  330. if pay_app_ali: # type
  331. item.update({'aliPayApp': pay_app_ali.to_dict()}) # type: AliApp
  332. else:
  333. item.update({'aliPayApp': AliApp.get_null_app().to_dict()})
  334. try:
  335. pay_app_wechat = agent.my_wechat_pay_app
  336. except Exception as e:
  337. pay_app_wechat = None
  338. if pay_app_wechat:
  339. item.update({'wechatPayApp': pay_app_wechat.to_dict()})
  340. else:
  341. item.update({'wechatPayApp': WechatPayApp.get_null_app().to_dict()})
  342. dealer_manager_app = agent.wechatDealerManagerialApp or WechatManagerApp.get_null_app() # type: WechatManagerApp
  343. item.update({'dealer': dealer_manager_app.to_dict()})
  344. user_manager_app = agent.wechatUserManagerialApp or WechatUserManagerApp.get_null_app() # type: WechatUserManagerApp
  345. item.update({'user': user_manager_app.to_dict()})
  346. user_sub_manager_app = agent.wechatUserSubscribeManagerApp or WechatUserSubscribeManagerApp.get_null_app() # type: WechatUserSubscribeManagerApp
  347. item.update({'user_sub': user_sub_manager_app.to_dict()})
  348. dealer_sub_manager_app = agent.wechatDealerSubscribeManagerApp or WechatDealerSubscribeManagerApp.get_null_app() # type: WechatDealerSubscribeManagerApp
  349. item.update({'dealer_sub': dealer_sub_manager_app.to_dict()})
  350. # 监督号的配置
  351. moniAppList = []
  352. for appId in agent.moniAppList:
  353. try:
  354. moniApp = MoniApp.objects.get(appId = appId)
  355. except Exception, e:
  356. continue
  357. moniAppList.append({'appId': moniApp.appId, 'appName': moniApp.appName})
  358. item.update({'moniApps': moniAppList})
  359. pointList = []
  360. for key, switch in agent.moniAppCheckPointDict.items():
  361. try:
  362. point = MoniAppPoint.objects.get(key = key)
  363. except Exception, e:
  364. continue
  365. pointList.append({'name': point.name, 'key': key, 'switch': switch})
  366. item.update({'pointDict': pointList})
  367. if agent.is_primary:
  368. item.update({'isPrimary': True})
  369. dataList.insert(0, item)
  370. else:
  371. item.update({'isPrimary': False})
  372. dataList.append(item)
  373. return total, dataList
  374. @property
  375. def product_logo(self):
  376. if not self.productLogo:
  377. return self.primary_agent.productLogo
  378. else:
  379. return self.productLogo
  380. @property
  381. def product_name(self):
  382. if not self.productName:
  383. return self.primary_agent.productName
  384. else:
  385. return self.productName
  386. @staticmethod
  387. def get_agent(agentId):
  388. try:
  389. agent = Agent.objects.get(id = ObjectId(agentId)) # type: Agent
  390. return agent.to_dict()
  391. except DoesNotExist:
  392. logger.exception('can not find agent from db,id=%s' % agentId)
  393. return None
  394. def update(self, **kwargs):
  395. # type: (**Any)->int
  396. """
  397. 每次更新的时候确保正在更改支付网关的选项设置为关闭
  398. :param kwargs:
  399. :return:
  400. """
  401. return super(Agent, self).update(**kwargs)
  402. @staticmethod
  403. def update_agent(agentId, valueDict):
  404. """TODO REFACTOR"""
  405. if not valueDict:
  406. return True
  407. try:
  408. agent = Agent.objects(id = agentId).get()
  409. updated = agent.update(**valueDict)
  410. assert updated, u'更新失败'
  411. except Exception as e:
  412. logger.exception('update agent error=%s' % e)
  413. return False
  414. return True
  415. @staticmethod
  416. def record_cookie(agentId, response):
  417. agent = Agent.get_agent(agentId)
  418. if agent is None:
  419. return response
  420. response.set_cookie(key = 'agentLogoUrl',
  421. value = agent['productLogo'],
  422. max_age = 24 * 3600 * 30,
  423. domain = settings.COOKIE_DOMAIN)
  424. pn = urllib.quote(agent['productName'].encode('utf-8'))
  425. response.set_cookie(key = 'agentBrandName', value = pn, max_age = 24 * 3600 * 30,
  426. domain = settings.COOKIE_DOMAIN)
  427. response.set_cookie(key = 'agentId', value = agentId, max_age = 24 * 3600 * 30, domain = settings.COOKIE_DOMAIN)
  428. return response
  429. def put_cookie(self, response):
  430. logo = self.product_logo
  431. if not logo:
  432. logo = '/app/img/logo.png'
  433. response.set_cookie(key = 'agentLogoUrl', value = logo,
  434. max_age = 24 * 3600 * 30,
  435. domain = settings.COOKIE_DOMAIN)
  436. productName = self.product_name
  437. response.set_cookie(key = 'agentBrandName',
  438. value = urllib.quote(productName.encode('utf-8')),
  439. max_age = 24 * 3600 * 30,
  440. domain = settings.COOKIE_DOMAIN)
  441. response.set_cookie(key = 'agentId',
  442. value = str(self.id),
  443. max_age = 24 * 3600 * 30,
  444. domain = settings.COOKIE_DOMAIN)
  445. return response
  446. def get_dealers(self):
  447. """
  448. 获取旗下经销商
  449. :return:
  450. """
  451. Dealer = import_string('apps.web.dealer.models.Dealer')
  452. return Dealer.objects(agentId = str(self.id))
  453. @property
  454. def primary_agent_id(self):
  455. return self.manager.primeAgentId
  456. @property
  457. def manager(self): # type:() -> Manager
  458. if not hasattr(self, '__manager__'):
  459. manager = Manager.objects(id=self.managerId).first()
  460. if not manager:
  461. raise NoManagerFound(manager_id=self.managerId)
  462. setattr(self, '__manager__', manager)
  463. return getattr(self, '__manager__')
  464. @property
  465. def primary_agent(self): # type:() -> Agent
  466. if not hasattr(self, '__my_prime_agent__'):
  467. primeAgent = Agent.objects(id = self.primary_agent_id).first()
  468. if not primeAgent:
  469. raise NoAgentFound(agent_id = self.primary_agent_id)
  470. setattr(self, '__my_prime_agent__', primeAgent)
  471. return getattr(self, '__my_prime_agent__')
  472. @property
  473. def is_primary(self):
  474. """
  475. 不需要去多查询一次数据库 只需要找到manager即可
  476. """
  477. return self.is_equal(self.primary_agent)
  478. @property
  479. def is_in_domain(self):
  480. try:
  481. return self.manager.domain == settings.MY_DOMAIN
  482. except Exception as e:
  483. logger.error('get manager failure. error = %s; id = %s' % (str(e), str(self.id)))
  484. return False
  485. @classmethod
  486. def from_agent(cls, agent, app_type, **kwargs):
  487. param_key = '{app_type}_app'.format(app_type = app_type)
  488. attr_or_func = getattr(agent, param_key)
  489. if hasattr(attr_or_func, '__call__'):
  490. return attr_or_func(**kwargs)
  491. else:
  492. return attr_or_func
  493. @classmethod
  494. def factory(cls, **kwargs):
  495. factory_type = kwargs.pop('factory_type')
  496. if factory_type == 'app':
  497. app_type = kwargs.pop('app_type')
  498. return lambda agent: cls.from_agent(agent = agent, app_type = app_type, **kwargs)
  499. elif factory_type == 'withdraw_source_key':
  500. pay_app = kwargs.pop('pay_app')
  501. return lambda agent: getattr(agent, 'withdraw_source_key')(pay_app)
  502. else:
  503. raise InvalidParameter(u'参数错误')
  504. @staticmethod
  505. def get_inhouse_prime_agent():
  506. # type: ()->Agent
  507. """
  508. 为了方便识别,如果获取默认代理商失败,需要报错为默认代理商找不到
  509. """
  510. try:
  511. return Agent.objects(id = str(settings.MY_PRIMARY_AGENT_ID)).get()
  512. except DoesNotExist:
  513. raise PrimaryAgentDoesNotExist('failed to get default primary agent')
  514. @property
  515. def inhouse_prime_agent(self):
  516. if str(self.id) == str(settings.MY_PRIMARY_AGENT_ID):
  517. return self
  518. else:
  519. return Agent.get_inhouse_prime_agent()
  520. @property
  521. def customizedCashflowAllowable(self):
  522. """
  523. 目前withdrawSourceKey是使用微信商户来标记. 所以资金池场景下仅
  524. 判断微信商户是否支持就可以。在资金池场景下, 必须配置微信支付
  525. :return:
  526. """
  527. return self.customizedWechatCashflowAllowable
  528. def wechat_mini_env_pay_app(self, role = None, pay_app_type = None):
  529. # type: (str, str)->WechatMiniApp
  530. if not self.customizedWechatMiniAllowable:
  531. primary_agent = self.primary_agent
  532. if self.is_equal(primary_agent):
  533. return self.inhouse_prime_agent.wechat_mini_env_pay_app(role, pay_app_type)
  534. else:
  535. return primary_agent.wechat_mini_env_pay_app(role, pay_app_type)
  536. if self.customizedWechatMiniAllowable:
  537. if not self.customizedCashflowAllowable:
  538. raise Exception(u'第三方支付配置错误(1001)')
  539. app = self.payAppWechatMini.fetch()
  540. if app and app.enable and app.valid:
  541. app.occupantId = str(self.id)
  542. app.occupant = self
  543. return app
  544. app = self.wechatMiniApp # type: WechatMiniApp
  545. if app and app.enable and app.valid:
  546. app.occupantId = str(self.id)
  547. app.occupant = self
  548. return app
  549. raise Exception(u'第三方支付配置错误(1002)')
  550. else:
  551. return WechatMiniApp.get_null_app()
  552. def wechat_env_pay_app(self, role = None, pay_app_type = None):
  553. # type: (Optional[None, str], Optional[None, str])->Optional[cast(PayAppBase)]
  554. assert not (role and pay_app_type), 'role and app_type must not have value in the same time.'
  555. if not self.customizedCashflowAllowable:
  556. if pay_app_type:
  557. _pay_app_type = pay_app_type
  558. _role = None
  559. else:
  560. custom = self.payType.get('custom', False)
  561. if custom:
  562. if role in self.payType and AppPlatformType.WECHAT in self.payType[role]:
  563. _pay_app_type = self.payType[role][AppPlatformType.WECHAT]
  564. _role = None
  565. else:
  566. _pay_app_type = None
  567. _role = role
  568. else:
  569. _pay_app_type = None
  570. _role = role
  571. primary_agent = self.primary_agent
  572. if self.is_equal(primary_agent):
  573. return self.inhouse_prime_agent.wechat_env_pay_app(_role, _pay_app_type)
  574. else:
  575. return primary_agent.wechat_env_pay_app(_role, _pay_app_type)
  576. if role:
  577. app_pay_type = self.my_pay_type(role, AppPlatformType.WECHAT)
  578. else:
  579. app_pay_type = pay_app_type
  580. if app_pay_type == PayAppType.WECHAT:
  581. app = self.my_wechat_pay_app # type: WechatPayApp
  582. if not app.enable or not app.valid:
  583. raise Exception(u'系统配置错误(第三方支付)')
  584. return app
  585. if app_pay_type == PayAppType.JD_AGGR:
  586. app = self.my_jd_aggre_pay_app # type: JDAggrePayApp
  587. if not app.enable or not app.valid:
  588. raise Exception(u'系统配置错误(第三方支付)')
  589. return app
  590. raise Exception(u'系统配置错误(第三方支付)')
  591. def alipay_env_pay_app(self, role = None, pay_app_type = None):
  592. assert not (role and pay_app_type), 'role and app_type must not have value in the same time.'
  593. if not self.customizedCashflowAllowable:
  594. # 如果代理商配置了需要的支付类型, 以代理商配置的为准
  595. if pay_app_type:
  596. _pay_app_type = pay_app_type
  597. _role = None
  598. else:
  599. custom = self.payType.get('custom', False)
  600. if custom:
  601. if role in self.payType and AppPlatformType.ALIPAY in self.payType[role]:
  602. _pay_app_type = self.payType[role][AppPlatformType.ALIPAY]
  603. _role = None
  604. else:
  605. _pay_app_type = None
  606. _role = role
  607. else:
  608. _pay_app_type = None
  609. _role = role
  610. primary_agent = self.primary_agent
  611. if self.is_equal(primary_agent):
  612. return self.inhouse_prime_agent.alipay_env_pay_app(_role, _pay_app_type)
  613. else:
  614. return primary_agent.alipay_env_pay_app(_role, _pay_app_type)
  615. if role:
  616. app_pay_type = self.my_pay_type(role, AppPlatformType.ALIPAY)
  617. else:
  618. app_pay_type = pay_app_type
  619. if app_pay_type == PayAppType.ALIPAY:
  620. app = self.my_ali_pay_app # type: AliApp
  621. if not app.enable or not app.valid:
  622. raise Exception(u'系统配置错误(第三方支付)')
  623. return app
  624. if app_pay_type == PayAppType.JD_AGGR:
  625. app = self.my_jd_aggre_pay_app # type: JDAggrePayApp
  626. if not app.enable or not app.valid:
  627. raise Exception(u'系统配置错误(第三方支付)')
  628. return app
  629. raise Exception(u'系统配置错误(第三方支付)')
  630. def jd_env_pay_app(self, role = None, pay_app_type = None):
  631. assert not (role and pay_app_type), 'role and app_type must not have value in the same time.'
  632. if not self.customizedCashflowAllowable:
  633. if pay_app_type:
  634. _pay_app_type = pay_app_type
  635. _role = None
  636. else:
  637. custom = self.payType.get('custom', False)
  638. if custom:
  639. if role in self.payType and AppPlatformType.JD in self.payType[role]:
  640. _pay_app_type = self.payType[role][AppPlatformType.JD]
  641. _role = None
  642. else:
  643. _pay_app_type = None
  644. _role = role
  645. else:
  646. _pay_app_type = None
  647. _role = role
  648. primary_agent = self.primary_agent
  649. if self.is_equal(primary_agent):
  650. return self.inhouse_prime_agent.jd_env_pay_app(_role, _pay_app_type)
  651. else:
  652. return primary_agent.jd_env_pay_app(_role, _pay_app_type)
  653. if role:
  654. app_pay_type = self.my_pay_type(role, AppPlatformType.JD)
  655. else:
  656. app_pay_type = pay_app_type
  657. if app_pay_type == PayAppType.JD_AGGR:
  658. app = self.my_jd_aggre_pay_app # type: JDAggrePayApp
  659. if not app.enable or not app.valid:
  660. raise Exception(u'系统配置错误(第三方支付)')
  661. return app
  662. raise Exception(u'系统配置错误(第三方支付)')
  663. def _check_wechat_withdraw(self):
  664. my_app = self.my_wechat_pay_app
  665. if not my_app.enable:
  666. raise Exception(u'系统配置错误(三方支付)')
  667. @property
  668. def my_wechat_withdraw_app(self):
  669. my_app = None
  670. if self.withdrawApps:
  671. source_key = self.withdraw_source_key()
  672. if source_key in self.withdrawApps:
  673. my_app = self.withdrawApps[source_key].wechat_app
  674. if not my_app:
  675. my_app = self.my_wechat_pay_app
  676. if (not my_app.valid) or (not my_app.enable):
  677. raise Exception(u'配置错误(第三方支付)')
  678. my_app.occupant = self
  679. my_app.occupantId = str(self.id)
  680. return my_app
  681. @property
  682. def wechat_withdraw_app(self):
  683. if not self.customizedCashflowAllowable:
  684. primary_agent = self.primary_agent
  685. if self.is_equal(primary_agent):
  686. return self.inhouse_prime_agent.wechat_withdraw_app
  687. else:
  688. return primary_agent.wechat_withdraw_app
  689. return self.my_wechat_withdraw_app
  690. def withdraw_source_key(self, pay_app = None):
  691. # type:(PayAppBase)->str
  692. if not pay_app:
  693. my_app = self.wechat_env_pay_app(pay_app_type = PayAppType.WECHAT)
  694. if (not my_app) or (not my_app.valid) or (not my_app.enable):
  695. raise Exception(u'配置错误(第三方支付)')
  696. else:
  697. agent = pay_app.occupant # type: Agent
  698. my_app = agent.my_wechat_pay_app # type: WechatPayApp
  699. if (not my_app) or (not my_app.valid) or (not my_app.enable):
  700. raise Exception(u'配置错误(第三方支付)')
  701. if hasattr(my_app, '__source_key__'):
  702. return APP_KEY_DELIMITER.join(
  703. [WithdrawGateway.LEDGER_PREFIX, my_app.pay_app_type, getattr(my_app, '__source_key__')])
  704. else:
  705. raise AttributeError('no __source_key__ attribute')
  706. # if my_app.inhouse:
  707. # return settings.MY_PRIMARY_AGENT_WALLET_KEY # 所有平台inhouse资金池全部使用固定的资金池KEY
  708. # else:
  709. # return APP_KEY_DELIMITER.join(
  710. # [WithdrawGateway.LEDGER_PREFIX, my_app.pay_app_type, getattr(my_app, '__source_key__')])
  711. @classmethod
  712. def _parse_source_key(cls, source_key):
  713. # type: (str)->tuple
  714. tokens = source_key.split(APP_KEY_DELIMITER)
  715. return True if tokens[0].startswith(WithdrawGateway.LEDGER_PREFIX) else False, tokens[1], tokens[2], tokens[3:]
  716. # if source_key == settings.MY_PRIMARY_AGENT_WALLET_KEY:
  717. # is_ledger, pay_app_type, occupant_id = True, PayAppType.WECHAT, settings.MY_PRIMARY_AGENT_WALLET_KEY
  718. # else:
  719. # tokens = source_key.split(APP_KEY_DELIMITER)
  720. # is_ledger, pay_app_type, occupant_id = True if tokens[0].startswith(
  721. # WithdrawGateway.LEDGER_PREFIX) else False, tokens[1], tokens[2]
  722. @property
  723. def disclaimerVersionFormat(self):
  724. return "{}_{}"
  725. @property
  726. def disclaimerVersion(self):
  727. verFormat = self.disclaimerVersionFormat
  728. return verFormat.format(str(self.id), self.disclaimer_version)
  729. @disclaimerVersion.setter
  730. def disclaimerVersion(self, value):
  731. try:
  732. self.update(disclaimer_version = value)
  733. except Exception as e:
  734. logger.error(
  735. "set disclaimer version error, agent is <{}>, version is <{}>, error is ".format(self, value, e))
  736. raise e
  737. @property
  738. def disclaimerContent(self):
  739. return self.disclaimer
  740. @disclaimerContent.setter
  741. def disclaimerContent(self, value):
  742. try:
  743. self.update(disclaimer = value)
  744. except Exception as e:
  745. logger.error("set disclaimer content error, agent is <{}>. error is <{}>".format(self, e))
  746. raise e
  747. @property
  748. def disclaimerAgent(self):
  749. """
  750. 获取设置了免责声明的代理商
  751. :return:
  752. """
  753. if self.disclaimer:
  754. return self
  755. primeAgent = self.primary_agent
  756. if primeAgent.disclaimer:
  757. return primeAgent
  758. inhousePrimeAgent = self.inhouse_prime_agent
  759. return inhousePrimeAgent
  760. @classmethod
  761. def get_disclaimer(cls, agentId):
  762. """
  763. 获取 代理商的 免责声明
  764. :param agentId:
  765. :return:
  766. """
  767. agent = cls.objects.get(id = agentId)
  768. if not agent.needDisclaimer:
  769. return AgentDisclaimer(content = "", version = agent.disclaimerVersion)
  770. agent = agent.disclaimerAgent
  771. content = agent.disclaimerContent
  772. disclaimer = AgentDisclaimer(content = content, version = agent.disclaimerVersion)
  773. return disclaimer
  774. @classmethod
  775. def withdraw_gateway_list(cls, source_key):
  776. is_ledger, pay_app_type, occupant_id, tokens = cls._parse_source_key(source_key)
  777. if not is_ledger:
  778. return is_ledger, {
  779. 'alipay': WithdrawGateway(AliApp.get_null_app(), False),
  780. 'wechat': WithdrawGateway(WechatPayApp.get_null_app(), False),
  781. 'wechatV3': WithdrawGateway(WechatPayApp.get_null_app(), False)
  782. }
  783. if pay_app_type != PayAppType.WECHAT:
  784. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1001)')
  785. agent = cls.objects(id = occupant_id).first()
  786. if not agent:
  787. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1002)')
  788. if not agent.withdrawApps:
  789. return is_ledger, {
  790. 'alipay': None,
  791. 'wechat': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger, gateway_version = 'v1'),
  792. 'wechatV3': agent.my_wechat_pay_app.new_withdraw_gateway(is_ledger = is_ledger, gateway_version = 'v3')
  793. }
  794. else:
  795. if source_key not in agent.withdrawApps:
  796. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服(1003)')
  797. withdraw_entity = agent.withdrawApps[source_key] # type: WithdrawEntity
  798. withdraw_entity.alipay_app.occupantId = str(agent.id)
  799. withdraw_entity.alipay_app.occupant = agent
  800. withdraw_entity.wechat_app.occupantId = str(agent.id)
  801. withdraw_entity.wechat_app.occupant = agent
  802. return is_ledger, {
  803. 'alipay': withdraw_entity.alipay_app.new_withdraw_gateway(is_ledger = is_ledger),
  804. 'wechat': withdraw_entity.wechat_app.new_withdraw_gateway(
  805. is_ledger = is_ledger, gateway_version = 'v1'),
  806. 'wechatV3': withdraw_entity.wechat_app.new_withdraw_gateway(
  807. is_ledger = is_ledger, gateway_version = 'v3')
  808. }
  809. @staticmethod
  810. def get_platform_wechat_manager_app():
  811. agent = Agent.get_inhouse_prime_agent()
  812. return agent.platform_wechat_manager_app
  813. @property
  814. def platform_wechat_manager_app(self):
  815. agent = self.inhouse_prime_agent
  816. app = agent.wechatDealerManagerialApp
  817. if not app:
  818. raise Exception(u'公众号配置错误')
  819. app.occupantId = str(agent.id)
  820. return app
  821. @staticmethod
  822. def get_inhouse_wechat_user_manager_app():
  823. agent = Agent.get_inhouse_prime_agent()
  824. return agent.inhouse_wechat_user_manager_app
  825. @property
  826. def inhouse_wechat_user_manager_app(self):
  827. agent = self.inhouse_prime_agent
  828. app = agent.wechatUserManagerialApp
  829. if not app:
  830. raise Exception(u'公众号配置错误')
  831. app.occupantId = str(agent.id)
  832. return app
  833. @property
  834. def wechat_auth_app(self):
  835. """
  836. 标识用户只用平台的公众号对应APPID. 部分用户由于前期原因配置了自己的
  837. 该接口仅做兼容
  838. :return:
  839. """
  840. if self.wechatLoginAuthApp:
  841. app = self.wechatLoginAuthApp # type: WechatAuthApp
  842. app.occupantId = str(self.id)
  843. return app
  844. else:
  845. primary_agent = self.primary_agent
  846. if self.is_equal(primary_agent):
  847. return self.inhouse_prime_agent.wechat_auth_app
  848. else:
  849. return primary_agent.wechat_auth_app
  850. @property
  851. def wechat_user_manager_app(self):
  852. if self.customizedUserGzhAllowable:
  853. if self.wechatUserManagerialApp:
  854. app = self.wechatUserManagerialApp # type: WechatManagerApp
  855. app.occupantId = str(self.id)
  856. app.occupant = self
  857. return app
  858. else:
  859. raise Exception(u'系统配置错误(wechat_user_manager_app)')
  860. else:
  861. primary_agent = self.primary_agent
  862. if self.is_equal(primary_agent):
  863. inhouse_agent = self.inhouse_prime_agent
  864. if inhouse_agent.customizedUserGzhAllowable:
  865. return inhouse_agent.wechat_user_manager_app
  866. else:
  867. raise Exception(u'系统未配置(wechat_user_manager_app)')
  868. else:
  869. return primary_agent.wechat_user_manager_app
  870. @property
  871. def wechat_user_messager_app(self):
  872. if self.customizedUserGzhAllowable or self.customizedUserSubGzhAllowable:
  873. if self.customizedUserGzhAllowable:
  874. if self.wechatUserManagerialApp:
  875. app = self.wechatUserManagerialApp # type: WechatUserManagerApp
  876. app.occupantId = str(self.id)
  877. app.occupant = self
  878. return app
  879. else:
  880. raise Exception(u'系统配置错误(wechat_user_manager_app)')
  881. if self.customizedUserSubGzhAllowable:
  882. if self.wechatUserSubscribeManagerApp:
  883. app = self.wechatUserSubscribeManagerApp # type: WechatUserSubscribeManagerApp
  884. app.occupantId = str(self.id)
  885. app.occupant = self
  886. return app
  887. else:
  888. raise Exception(u'系统配置错误(wechat_user_subscribe_manager_app)')
  889. else:
  890. primary_agent = self.primary_agent
  891. if self.is_equal(primary_agent):
  892. inhouse_agent = self.inhouse_prime_agent
  893. if inhouse_agent.customizedUserGzhAllowable or inhouse_agent.customizedUserSubGzhAllowable:
  894. return inhouse_agent.wechat_user_messager_app
  895. else:
  896. raise Exception(u'系统未配置(wechat_user_manager_app|wechat_user_subscribe_manager_app)')
  897. else:
  898. return primary_agent.wechat_user_messager_app
  899. @property
  900. def wechat_manager_app(self):
  901. if self.customizedDealerGzhAllowable:
  902. if self.wechatDealerManagerialApp:
  903. app = self.wechatDealerManagerialApp # type: WechatManagerApp
  904. app.occupantId = str(self.id)
  905. return app
  906. else:
  907. raise Exception(u'系统配置错误(wechat_dealer_manager_app, %s)' % str(self.id))
  908. else:
  909. primary_agent = self.primary_agent
  910. if self.is_equal(primary_agent):
  911. return self.inhouse_prime_agent.wechat_manager_app
  912. else:
  913. return primary_agent.wechat_manager_app
  914. @property
  915. def wechat_user_subscribe_manager_app(self):
  916. if self.customizedUserSubGzhAllowable:
  917. if self.wechatUserSubscribeManagerApp:
  918. app = self.wechatUserSubscribeManagerApp # type: WechatManagerApp
  919. app.occupantId = str(self.id)
  920. app.occupant = self
  921. return app
  922. else:
  923. raise Exception(u'系统配置错误(wechat_user_subscribe_manager_app)')
  924. else:
  925. primary_agent = self.primary_agent
  926. if self.is_equal(primary_agent):
  927. inhouse_agent = self.inhouse_prime_agent
  928. if inhouse_agent.customizedUserSubGzhAllowable:
  929. return inhouse_agent.wechat_user_subscribe_manager_app
  930. else:
  931. raise Exception(u'系统未配置(wechat_user_subscribe_manager_app)')
  932. else:
  933. return primary_agent.wechat_user_subscribe_manager_app
  934. @property
  935. def wechat_dealer_subscribe_manager_app(self):
  936. if self.customizedDealerSubGzhAllowable:
  937. if self.wechatDealerSubscribeManagerApp:
  938. app = self.wechatDealerSubscribeManagerApp # type: WechatManagerApp
  939. app.occupantId = str(self.id)
  940. app.occupant = self
  941. return app
  942. else:
  943. raise Exception(u'系统配置错误(wechat_user_manager_app)')
  944. else:
  945. primary_agent = self.primary_agent
  946. if self.is_equal(primary_agent):
  947. return self.inhouse_prime_agent.wechatDealerSubscribeManagerApp
  948. else:
  949. return primary_agent.wechatDealerSubscribeManagerApp
  950. def get_user_sub_template_id_list(self):
  951. if not self.customizedUserSubGzhAllowable:
  952. return []
  953. if not self.wechatUserSubscribeManagerApp.templateIdMap:
  954. return []
  955. else:
  956. try:
  957. return map(lambda _: _['templateId'], self.wechatUserSubscribeManagerApp.templateIdMap.values())
  958. except Exception as e:
  959. logger.info('error e=<{}>'.format(e))
  960. return []
  961. def get_dealer_sub_template_id_list(self):
  962. if not self.customizedDealerSubGzhAllowable:
  963. return []
  964. if not self.wechatUserSubscribeManagerApp.templateIdMap:
  965. return []
  966. else:
  967. try:
  968. return map(lambda _: _['templateId'], self.wechatDealerSubscribeManagerApp.templateIdMap.values())
  969. except Exception as e:
  970. logger.info('error e=<{}>'.format(e))
  971. return []
  972. @property
  973. def alipay_auth_app(self):
  974. if not self.customizedAlipayCashflowAllowable:
  975. primary_agent = self.primary_agent
  976. if self.is_equal(primary_agent):
  977. return self.inhouse_prime_agent.alipay_auth_app
  978. else:
  979. return primary_agent.alipay_auth_app
  980. app = self.my_ali_pay_app # type: AliApp
  981. logger.debug("app id is:{}".format(str(app.id)))
  982. if not app.valid:
  983. raise Exception(u'系统配置错误(第三方支付)')
  984. app.occupantId = str(self.id)
  985. return app
  986. @property
  987. def jd_auth_app(self):
  988. """
  989. 标识用户只用平台的公众号对应APPID. 部分用户由于前期原因配置了自己的
  990. 该接口仅做兼容
  991. :return:
  992. """
  993. if self.jdAuthApp:
  994. app = self.jdAuthApp # type: JDAuthApp
  995. app.occupantId = str(self.id)
  996. return app
  997. else:
  998. primary_agent = self.primary_agent
  999. if self.is_equal(primary_agent):
  1000. return self.inhouse_prime_agent.jdAuthApp
  1001. else:
  1002. return primary_agent.jdAuthApp
  1003. @property
  1004. def wechat_mini_auth_app(self):
  1005. if not self.customizedWechatMiniAllowable:
  1006. primary_agent = self.primary_agent
  1007. if self.is_equal(primary_agent):
  1008. return self.inhouse_prime_agent.wechat_mini_auth_app
  1009. else:
  1010. return primary_agent.wechat_mini_auth_app
  1011. app = self.wechatMiniApp
  1012. if not app.valid:
  1013. raise Exception(u'系统配置错误(第三方支付)')
  1014. app.occupantId = str(self.id)
  1015. return app
  1016. def income_by_date(self, date, specific_source = None):
  1017. """
  1018. :param date:
  1019. :param specific_source:
  1020. :return:
  1021. """
  1022. reports = AgentIncomeReport.objects(agentId = str(self.id), date = date)
  1023. if specific_source:
  1024. reports = reports.filter(source = specific_source)
  1025. return RMB(reports.sum('amount'))
  1026. def today_income(self, specific_source = None):
  1027. today = datetime.datetime.now().strftime(Const.DATE_FMT)
  1028. return self.income_by_date(date = today, specific_source = specific_source)
  1029. def yesterday_income(self, specific_source = None):
  1030. yesterday = (datetime.datetime.now() - datetime.timedelta(days = 1)).strftime(Const.DATE_FMT)
  1031. return self.income_by_date(date = yesterday, specific_source = specific_source)
  1032. def aggregate_income(self, specific_source = None):
  1033. """
  1034. 获取聚合收入
  1035. :param specific_source: 特定source
  1036. :return:
  1037. """
  1038. if not specific_source:
  1039. return sum_rmb(self.aggregatedIncome.values())
  1040. else:
  1041. return sum_rmb([v for k, v in self.aggregatedIncome.items() if k == specific_source])
  1042. def _single_month_income(self, year, month, specific_source):
  1043. if not specific_source:
  1044. reports = AgentIncomeReport.objects(agentId = str(self.id), date__startswith = '%d-%02d' % (year, month))
  1045. else:
  1046. reports = AgentIncomeReport.objects(agentId = str(self.id), date__startswith = '%d-%02d' % (year, month),
  1047. source = specific_source)
  1048. return RMB(reports.sum('amount'))
  1049. def current_month_income(self, specific_source = None):
  1050. now = datetime.datetime.now()
  1051. return self._single_month_income(now.year, now.month, specific_source)
  1052. def monthly_income(self, specific_source = None):
  1053. """
  1054. 获得月报表
  1055. :param specific_source:
  1056. :return:
  1057. """
  1058. now = datetime.datetime.now()
  1059. years = {_ for _ in range(2017, now.year + 1)}
  1060. months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  1061. return {(year, month): self._single_month_income(year, month, specific_source)
  1062. for year, month in itertools.product(years, months)}
  1063. def is_equal(self, agent):
  1064. return str(self.id) == str(agent.id)
  1065. @property
  1066. def supported_device_types(self):
  1067. result = DeviceType.all(str(self.id))
  1068. if result:
  1069. return result
  1070. primary_agent = self.primary_agent
  1071. if not self.is_equal(primary_agent):
  1072. return primary_agent.supported_device_types
  1073. else:
  1074. return []
  1075. @property
  1076. def feature_boolean_map(self):
  1077. # type: ()->Dict[str, bool]
  1078. features = super(Agent, self).feature_boolean_map
  1079. if self.customizedWechatCashflowAllowable:
  1080. features.update({
  1081. 'show_withdraw_management': True
  1082. })
  1083. return features
  1084. @property
  1085. def hide_consume_kinds_dealer(self):
  1086. rv = []
  1087. if 'hiddenUsedTime' in self.features:
  1088. rv.append(DEALER_CONSUMPTION_AGG_KIND.DURATION)
  1089. if 'hidden_coins' in self.features:
  1090. rv.append(DEALER_CONSUMPTION_AGG_KIND.COIN)
  1091. left_features = set(self.features) - {'hiddenUsedTime', 'hidden_coins'}
  1092. for feature in left_features:
  1093. if feature.startswith('hide_') and feature.endswith('_for_dealer'):
  1094. kind = feature.replace('hide_', '').replace('_for_dealer', '')
  1095. if kind in DEALER_CONSUMPTION_AGG_KIND.choices():
  1096. rv.append(kind)
  1097. return rv
  1098. @property
  1099. def hide_consume_kinds_user(self):
  1100. rv = []
  1101. if 'hiddenUsedTime' in self.features:
  1102. rv.append(DEALER_CONSUMPTION_AGG_KIND.DURATION)
  1103. if 'hidden_coins' in self.features:
  1104. rv.append(DEALER_CONSUMPTION_AGG_KIND.COIN)
  1105. left_features = set(self.features) - {'hiddenUsedTime', 'hidden_coins'}
  1106. for feature in left_features:
  1107. if feature.startswith('hide_') and feature.endswith('_for_user'):
  1108. kind = feature.replace('hide_', '').replace('_for_user', '')
  1109. if kind in DEALER_CONSUMPTION_AGG_KIND.choices():
  1110. rv.append(kind)
  1111. return rv
  1112. @property
  1113. def withdraw_sms_provider(self):
  1114. return agentWithdrawSMSProvider
  1115. @property
  1116. def withdraw_sms_phone_number(self):
  1117. return str(self.username)
  1118. def incr_income(self, source, source_key, money):
  1119. # type: (str, str, RMB)->bool
  1120. assert isinstance(money, RMB), 'money has to be a RMB instance'
  1121. assert source in AGENT_INCOME_SOURCE.choices(), 'not support this source'
  1122. assert source_key, 'source key must not be none'
  1123. income_type = AgentConst.MAP_SOURCE_TO_TYPE[source]
  1124. query = {'_id': ObjectId(self.id)}
  1125. update = {
  1126. '$inc': {
  1127. 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount,
  1128. 'incomeMap.{source}'.format(source=source): money.mongo_amount,
  1129. '{filed}.{key}.balance'.format(filed=AgentConst.MAP_TYPE_TO_FIELD[income_type],
  1130. key=source_key): money.mongo_amount
  1131. },
  1132. }
  1133. result = self.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
  1134. return bool(result.modified_count == 1)
  1135. def decr_income(self, source, source_key, money): # type:(str, str, RMB) -> bool
  1136. """
  1137. 扣除代理商的收益
  1138. 必须由调用方保证不会调用重入
  1139. """
  1140. return self.incr_income(source, source_key, -money)
  1141. def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual,
  1142. recurrent):
  1143. # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
  1144. if income_type == AGENT_INCOME_TYPE.DEALER_WITHDRAW_FEE:
  1145. withdraw_fee_ratio = Permillage('0.00') # type: Permillage
  1146. else:
  1147. withdraw_fee_ratio = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO # type: Permillage
  1148. service_fee = amount * withdraw_fee_ratio.as_ratio # type: RMB
  1149. withdraw_agent = withdraw_gateway.occupant # type: Agent
  1150. has_bank_fee = withdraw_agent.dealerBankWithdrawFee and \
  1151. self.bankWithdrawFee and \
  1152. pay_type == WITHDRAW_PAY_TYPE.BANK
  1153. if has_bank_fee:
  1154. bank_trans_fee = min(RMB('25.00'), max(RMB('0.10'), amount * Permillage('1').as_ratio))
  1155. else:
  1156. bank_trans_fee = RMB('0.00')
  1157. actual_pay = amount - service_fee - bank_trans_fee # type: RMB
  1158. return WithdrawRecord.create(self,
  1159. withdraw_gateway = withdraw_gateway,
  1160. pay_entity = pay_entity,
  1161. source_key = source_key,
  1162. income_type = income_type,
  1163. pay_type = pay_type,
  1164. fund_map = {
  1165. 'amount': amount,
  1166. 'serviceFee': service_fee,
  1167. 'actualPay': actual_pay,
  1168. 'bankTransFee': bank_trans_fee,
  1169. 'withdrawFeeRatio': withdraw_fee_ratio,
  1170. 'partition': []
  1171. },
  1172. manual = manual,
  1173. recurrent = recurrent)
  1174. def new_withdraw_handler(self, record):
  1175. # type: (WithdrawRecord) -> WithdrawHandler
  1176. from apps.web.agent.withdraw import AgentWithdrawHandler
  1177. return AgentWithdrawHandler(self, record)
  1178. def get_bound_pay_openid(self, key):
  1179. # type: (str)->str
  1180. pay_openid_map = self.payOpenIdMap # type: dict
  1181. bound = pay_openid_map.get(key, BoundOpenInfo(openId = '')) # type: BoundOpenInfo
  1182. return str(bound.openId)
  1183. def set_bound_pay_openid(self, key, **payload):
  1184. # type: (str, Dict)->None
  1185. self.payOpenIdMap[key] = BoundOpenInfo(**payload)
  1186. def get_online_moni_app(self):
  1187. apps = MoniApp.objects.filter(appId__in = self.moniAppList, status=MoniAppStatus.ADDING).order_by('-priority')
  1188. if apps.count() == 0:
  1189. return {}
  1190. app = apps.first() # type: MoniApp
  1191. return {
  1192. 'appId': app.appId,
  1193. 'secret': app.secret,
  1194. 'title': app.title,
  1195. 'appName': app.appName,
  1196. 'desc': app.desc
  1197. }
  1198. @property
  1199. def agentIds(self):
  1200. # 自身 有agentId 配置的有公众号的,返回自身的id, 没有配置公众号的, 找到厂商的祝代理商的id 以及自身的id返回
  1201. if hasattr(self, "wechatUserManagerialApp") and self.wechatUserManagerialApp:
  1202. return [str(self.id)]
  1203. manager = Manager.objects.get(id = self.managerId)
  1204. agent = Agent.objects.get(id = str(manager.primeAgentId))
  1205. return [str(agent.id), str(self.id)]
  1206. def is_my_product_user(self, user):
  1207. return str(self.id) == user.productAgentId
  1208. @property
  1209. def my_avatar(self):
  1210. if self.avatar:
  1211. return self.avatar
  1212. logo = self.product_logo
  1213. if not logo:
  1214. logo = ''
  1215. return logo
  1216. def check_merchant_conditions(self): # type:()->None
  1217. """ 商户确认开通前的检查 """
  1218. if not self.ledgerAppJDAggre:
  1219. raise MerchantError(u"商户尚未绑定,开通失败")
  1220. def confirm_merchant(self, source):
  1221. """ 商户已经是确认状态所需要处理的事情 """
  1222. if not source.support_jdaggre():
  1223. return
  1224. if not source.owner == self:
  1225. return
  1226. # 首先获取自己的商户
  1227. app = source.create_app()
  1228. self.ledgerAppJDAggre = app
  1229. return self.save()
  1230. def success_merchant(self):
  1231. """ 确认商户开通的动作 """
  1232. return
  1233. @property
  1234. def merchantRegister(self):
  1235. return self
  1236. @property
  1237. def abnormal(self):
  1238. return self.status == self.Status.ABNORMAL
  1239. @property
  1240. def myLedgerMerchantApp(self):
  1241. """
  1242. 商户分账APP
  1243. """
  1244. if not self.ledgerAppJDAggre:
  1245. if not self.payAppJDAggre:
  1246. return None
  1247. else:
  1248. return self.payAppJDAggre.fetch()
  1249. return self.ledgerAppJDAggre.fetch()
  1250. @property
  1251. def deviceIncomeShow(self):
  1252. """
  1253. 兼容之前数据库字段的方式
  1254. """
  1255. if "deviceIncomeShow" in self.features:
  1256. return True
  1257. return getattr(self, '_deviceIncomeShow', False)
  1258. @deviceIncomeShow.setter
  1259. def deviceIncomeShow(self, value):
  1260. """
  1261. 兼容之前的数据库 防止报错
  1262. """
  1263. setattr(self, '_deviceIncomeShow', value)
  1264. class AgentIncomeReport(Searchable):
  1265. agentId = StringField(verbose_name = '代理商ID')
  1266. detail = StrictDictField(verbose_name = '详情', default = {})
  1267. source = StringField(verbose_name = '收入来源')
  1268. amount = MonetaryField(verbose_name = '数额', default = RMB(0))
  1269. mchid = StringField(verbose_name = '资金账号')
  1270. date = StringField(verbose_name = '日期', default = lambda: datetime.datetime.now().strftime(Const.DATE_FMT))
  1271. dateTimeAdded = DateTimeField(verbose_name = '生成时间', default = datetime.datetime.now)
  1272. meta = {"collection": "agent_income_reports", "db_alias": "report"}
  1273. def __repr__(self):
  1274. return '<AgentIncomeReport agentId=%s, source=%s, date=%s>' % (self.agentId, self.source, self.date)
  1275. @property
  1276. def title(self):
  1277. if self.source == AGENT_INCOME_SOURCE.DEALER_WITHDRAW_FEE:
  1278. return u'提现收益-经销商({}) 提现金额({})'.format(str(self.detail.get('name', '')), str(self.detail.get('withdrawAmount', '')))
  1279. elif self.source == AGENT_INCOME_SOURCE.DEALER_CARD_FEE:
  1280. return u'流量卡收益-经销商(%s) 充值(%s)' % (self.detail.get('name', ''), self.detail.get('sum_of_price', ''))
  1281. elif self.source == AGENT_INCOME_SOURCE.AD:
  1282. return u'广告收益-广告(%s) 设备(%s) 经销商(%s)' \
  1283. % (self.detail.get('adId', ''), self.detail.get('logicalCode', ''), self.detail.get('dealer', ''))
  1284. elif self.source == AGENT_INCOME_SOURCE.DEALER_DEVICE_FEE:
  1285. return u'设备收益-经销商(%s) 设备(%s) 地址(%s)' \
  1286. % (self.detail.get('name', ''), self.detail.get('logicalCode', ''), self.detail.get('groupName', ''))
  1287. elif self.source == AGENT_INCOME_SOURCE.INSURANCE:
  1288. return u'保险收益-经销商({}) 地址({})'.format(self.detail.get('name', ''), self.detail.get('groupName', ''))
  1289. @classmethod
  1290. def get_income_list(cls, **filters):
  1291. if 'endDate' in filters:
  1292. filters['date__lt'] = filters.pop('endDate')
  1293. return cls.objects(**filters).order_by('-createdTime')
  1294. class MoniApp(Searchable):
  1295. # 公众号的原始属性 其中appToken用于微信验证服务器 由我们自行设置
  1296. appName = StringField(verbose_name=u"公众号的名称", default="")
  1297. appid = StringField(verbose_name=u"appId", unique=True)
  1298. rawAppId = StringField(verbose_name=u"公众号的微信号", unique=True)
  1299. secret = StringField(verbose_name=u"秘钥", default="")
  1300. appToken = StringField(verbose_name="token", default="")
  1301. appType = StringField(verbose_name="公众号类型", default="wechat")
  1302. agentId = StringField(verbose_name=u"公众号所属的Agent")
  1303. priority = IntField(verbose_name="加粉的数量", default=0)
  1304. status = IntField(verbose_name="当前的状态", default=MoniAppStatus.ADDING, choices=MoniAppStatus.choices())
  1305. desc = StringField(verbose_name='展示描述', default = '')
  1306. title = StringField(verbose_name=u'展示加粉的说明title', default="")
  1307. # TODO 需要将agentId 以及status 设置为联合索引确保唯一性
  1308. meta = {"collection": "moni_app", "db_alias": "default"}
  1309. def __str__(self):
  1310. return "<{}>-<{}>".format(self.appName, self.rawAppId)
  1311. @property
  1312. def appId(self):
  1313. return self.appid
  1314. @property
  1315. def occupantId(self):
  1316. return self.agentId
  1317. @classmethod
  1318. def get_app_by_raw(cls, rawAppId):
  1319. try:
  1320. app = cls.objects.get(rawAppId=rawAppId)
  1321. except DoesNotExist:
  1322. return None
  1323. return app
  1324. @classmethod
  1325. def get_app_by_agent(cls, agentId):
  1326. """
  1327. 获取代理商设置的 moniApp
  1328. 找到加粉符合条件的最少的一个
  1329. """
  1330. agentId = str(agentId)
  1331. return cls.objects.filter(agentId=agentId, status=MoniAppStatus.ADDING).first()
  1332. @classmethod
  1333. def get_inhouse_app(cls):
  1334. return cls.get_app_by_agent(agentId=settings.MY_PRIMARY_AGENT_ID)
  1335. def to_dict(self):
  1336. return {
  1337. 'appid': self.appid,
  1338. 'secret': self.secret,
  1339. 'title': self.title,
  1340. 'appName': self.appName,
  1341. 'desc': self.desc
  1342. }
  1343. @classmethod
  1344. def subscribe(cls, app):
  1345. cls.objects.filter(appid=str(app.appid)).update(inc__priority=1)
  1346. @classmethod
  1347. def unSubscribe(cls, app):
  1348. cls.objects.filter(appid=str(app.appid)).update(dec__priority=1)
  1349. class MoniAppPoint(Searchable):
  1350. key = StringField(verbose_name = 'key', default = '')
  1351. name = StringField(verbose_name = 'name', default = '')
  1352. desc = StringField(verbose_name = 'desc', default = '')
  1353. meta = {"collection": "moni_app_point", "db_alias": "default"}