models.py 61 KB

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