models.py 106 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import base64
  4. import datetime
  5. import logging
  6. import os
  7. from importlib import import_module
  8. import simplejson as json
  9. from django.conf import settings
  10. from django.core.cache import cache
  11. from django.utils.module_loading import import_string
  12. from mongoengine import StringField, DateTimeField, EmbeddedDocument, DictField, BooleanField, DynamicField, ListField, \
  13. IntField, LazyReferenceField, ObjectIdField, DynamicDocument, EmbeddedDocumentField
  14. from typing import List, cast, TYPE_CHECKING
  15. from apilib.monetary import RMB
  16. from apilib.utils import flatten
  17. from apilib.utils_json import json_loads, json_dumps
  18. from apilib.utils_string import encrypt_display
  19. from apilib.utils_sys import ThreadLock
  20. from apps import serviceCache
  21. from apps.web.constant import Const, PARTITION_ROLE
  22. from apps.web.core import PayAppType, APP_KEY_DELIMITER, ROLE
  23. from apps.web.core.db import Searchable
  24. from apps.web.exceptions import UserServerException
  25. from library.alipay import AliException, AliPay, DCAliPay
  26. from library.wechatpayv3 import update_certificates
  27. logger = logging.getLogger(__name__)
  28. if TYPE_CHECKING:
  29. from apps.web.merchant.models import MerchantSourceInfo, JDOpenApplyInfo
  30. from apps.web.dealer.models import Dealer
  31. class FAQ(Searchable):
  32. """
  33. 配置FAQ,首先让经销商|终端用户看到常用问题,找不到问题再联系客服
  34. """
  35. question = StringField(verbose_name = '问题')
  36. answer = StringField(verbose_name = '答案')
  37. target = StringField(verbose_name = '目标 := (Dealer|Agent|EndUser)', default = '*')
  38. devTypeId = StringField(verbose_name = '设备类型ID,默认为普适性问题,为空', default = '')
  39. images = ListField(verbose_name = '辅助说明图像')
  40. videos = ListField(verbose_name = '辅助说明视频')
  41. upvotes = IntField(verbose_name = '该FAQ有帮助')
  42. downvotes = IntField(verbose_name = '该FAQ没有帮助')
  43. meta = {'collection': 'faqs', 'db_alias': 'default'}
  44. def to_dict(self):
  45. return {
  46. 'id': str(self.id),
  47. 'question': self.question,
  48. 'answer': self.answer,
  49. 'target': self.target,
  50. 'images': self.images,
  51. 'videos': self.videos,
  52. 'devTypeId': self.devTypeId,
  53. 'upvotes': self.upvotes,
  54. 'downvotes': self.downvotes
  55. }
  56. @classmethod
  57. def get_by_target(cls, target):
  58. return cls.objects(target__in = ['*', target])
  59. class EmbeddedApp(EmbeddedDocument):
  60. meta = {
  61. 'abstract': True
  62. }
  63. appid = StringField(verbose_name = 'appid', default = '')
  64. secret = StringField(verbose_name = "secretId", default = '', max_length = 32)
  65. name = StringField(verbose_name = "name", default = '')
  66. companyName = StringField(verbose_name = "companyName", default = '')
  67. @property
  68. def debug(self):
  69. return False
  70. @property
  71. def occupantId(self):
  72. return getattr(self, '__occupant_id__', '')
  73. @occupantId.setter
  74. def occupantId(self, occupant_id):
  75. self.__occupant_id__ = occupant_id
  76. @property
  77. def occupant(self):
  78. return getattr(self, '__occupant__', None)
  79. @occupant.setter
  80. def occupant(self, occupant):
  81. self.__occupant__ = occupant
  82. @property
  83. def __valid_check__(self):
  84. return bool(self.appid and self.secret)
  85. @property
  86. def valid(self):
  87. if hasattr(self, '__valid__'):
  88. return getattr(self, '__valid__')
  89. else:
  90. if not hasattr(self, '__valid_check__'):
  91. raise AttributeError('no __valid_check__ attribute')
  92. else:
  93. return getattr(self, '__valid_check__')
  94. @valid.setter
  95. def valid(self, value):
  96. self.__valid__ = value
  97. @property
  98. def enable(self):
  99. return getattr(self, '__enable__', True)
  100. @enable.setter
  101. def enable(self, is_enable):
  102. self.__enable__ = is_enable
  103. @classmethod
  104. def get_null_app(cls):
  105. app = cls(appid = '', secret = '')
  106. app.enable = False
  107. app.valid = True
  108. return app
  109. @staticmethod
  110. def from_json_string(app_string):
  111. app_dict = json.loads(app_string)
  112. _model = app_dict.pop('model')
  113. _cls = import_string(_model)
  114. return _cls(**app_dict)
  115. class WechatMiniApp(EmbeddedApp):
  116. mchid = StringField(verbose_name = 'mchid', null = False)
  117. apikey = StringField(verbose_name = 'apikey', null = False)
  118. sslcert_path = StringField(verbose_name = u'PERM证书路径', null = False)
  119. sslkey_path = StringField(verbose_name = u'PKCS8 PERM私钥路径', null = False)
  120. sslCert = StringField(verbose_name = u'PERM证书内容', default = '')
  121. sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '')
  122. manual_withdraw = BooleanField(verbose_name = u'是否手动提现', default = False)
  123. debug = BooleanField(verbose_name = u'是否测试账号', default = False)
  124. def __repr__(self):
  125. return '<WechatMiniApp appid=%s, mchid=%s>' % (self.appid, self.mchid)
  126. @property
  127. def __valid_check__(self):
  128. return super(WechatMiniApp, self).__valid_check__ and bool(
  129. self.mchid and self.apikey and self.ssl_cert and self.ssl_key)
  130. @property
  131. def pay_app_type(self):
  132. return PayAppType.WECHAT_MINI
  133. @property
  134. def __gateway_key__(self):
  135. _ = [
  136. self.occupantId,
  137. self.appid,
  138. self.mchid,
  139. self.occupant.role
  140. ]
  141. return APP_KEY_DELIMITER.join(_)
  142. def to_dict(self):
  143. return {
  144. 'appid': self.appid,
  145. 'secret': self.secret,
  146. 'mchid': self.mchid,
  147. 'apikey': self.apikey,
  148. 'debug': self.debug,
  149. 'name': self.name,
  150. 'companyName': self.companyName
  151. }
  152. @classmethod
  153. def get_null_app(cls):
  154. app = cls(appid = '', secret = '', mchid = '', apikey = '', sslcert_path = '', sslkey_path = '')
  155. app.enable = False
  156. app.valid = True
  157. return app
  158. @classmethod
  159. def __from_gateway_key__(cls, occupant_id, tokens):
  160. # type: (str, List)->cast(PayAppBase)
  161. appid, mchid = tokens[0], tokens[1]
  162. # TODO 这个地方确实没法解决. 必须通过AGENT来加载
  163. Agent = import_string('apps.web.agent.models.Agent')
  164. agent = Agent.objects(id = str(occupant_id)).first() # type: Agent
  165. if not agent:
  166. raise UserServerException(u'系统配置错误(第三方支付)')
  167. app = agent.wechatMiniApp # type: WechatMiniApp
  168. if not app or not app.valid:
  169. raise UserServerException(u'系统配置错误,请联系平台客服')
  170. app.occupantId = occupant_id
  171. app.occupant = agent
  172. return app
  173. def new_gateway(self, app_platform_type):
  174. from apps.web.core.payment.wechat import WechatPaymentGateway
  175. return WechatPaymentGateway(self, app_platform_type)
  176. def new_auth_bridge(self):
  177. from apps.web.core.auth.wechat import WechatAuthBridge
  178. return WechatAuthBridge(app = self)
  179. @property
  180. def ssl_cert(self):
  181. if not hasattr(self, '__ssl_cert__'):
  182. if self.sslCert.startswith('-----BEGIN'):
  183. setattr(self, '__ssl_cert__', self.sslCert)
  184. elif self.sslcert_path.startswith('-----BEGIN'):
  185. setattr(self, '__ssl_cert__', self.sslcert_path)
  186. else:
  187. with open(self.sslcert_path) as fp:
  188. setattr(self, '__ssl_cert__', fp.read())
  189. return str(getattr(self, '__ssl_cert__'))
  190. @property
  191. def ssl_key(self):
  192. if not hasattr(self, '__ssl_key__'):
  193. if self.sslKey.startswith('-----BEGIN'):
  194. setattr(self, '__ssl_key__', self.sslKey)
  195. elif self.sslkey_path.startswith('-----BEGIN'):
  196. setattr(self, '__ssl_key__', self.sslkey_path)
  197. else:
  198. with open(self.sslkey_path) as fp:
  199. setattr(self, '__ssl_key__', fp.read())
  200. return str(getattr(self, '__ssl_key__'))
  201. class WechatAuthApp(EmbeddedApp):
  202. def __repr__(self): return '<WechatAuthApp appid=%s>' % (self.appid,)
  203. def to_dict(self):
  204. return {
  205. 'appid': self.appid,
  206. 'secret': self.secret,
  207. 'name': self.name,
  208. 'companyName': self.companyName
  209. }
  210. @property
  211. def client(self):
  212. if hasattr(self, '__client__'):
  213. return getattr(self, '__client__')
  214. class WechatManagerApp(EmbeddedApp):
  215. templateIdMap = DictField(verbose_name = "微信推送消息模版ID字典", default = {})
  216. MESSAGE_TEMPLATE = {
  217. 'feedback': {
  218. 'templateId': '',
  219. 'context': json.dumps({
  220. "first": {
  221. "value": u'${title}',
  222. "color": "#173177"
  223. },
  224. "keyword1": {
  225. "value": u'${nickname}',
  226. "color": "#173177"
  227. },
  228. "keyword2": {
  229. "value": '${feedbackTime}',
  230. "color": "#173177"
  231. },
  232. "remark": {
  233. "value": "客户就是上帝,请及时处理!",
  234. "color": "#173177"
  235. }
  236. })
  237. },
  238. 'daily_income': {
  239. 'templateId': '',
  240. 'context': json.dumps({
  241. 'first': {
  242. 'value': u'${title}',
  243. 'color': '#173177'
  244. },
  245. 'keyword1': {
  246. "value": '${reportTime}',
  247. "color": '#173177'
  248. },
  249. 'keyword2': {
  250. 'value': u'${report}',
  251. 'color': '#173177'
  252. },
  253. 'remark': {
  254. 'value': u'祝您今日工作愉快!',
  255. 'color': '#173177'
  256. }
  257. })
  258. },
  259. 'new_payment_order': {
  260. 'templateId': '',
  261. 'context': json.dumps({
  262. 'first': {
  263. 'value': u'${title}',
  264. 'color': '#173177'
  265. },
  266. 'keyword1': {
  267. 'value': u'${customer}',
  268. 'color': '#173177'
  269. },
  270. 'keyword2': {
  271. 'value': u'${income}',
  272. 'color': '#173177'
  273. },
  274. 'remark': {
  275. 'value': u'祝您生意兴隆!',
  276. 'color': '#173177'
  277. }
  278. })
  279. },
  280. 'abnormal_device_offline': {
  281. 'templateId': '',
  282. 'context': json.dumps({
  283. 'first': {
  284. 'value': u'${title}',
  285. 'color': '#173177'
  286. },
  287. 'keyword1': {
  288. "value": u'${device}',
  289. "color": '#173177'
  290. },
  291. 'keyword2': {
  292. 'value': '${notifyTime}',
  293. 'color': '#173177'
  294. },
  295. 'remark': {
  296. 'value': u'感谢您的使用,小客服随时为您服务',
  297. 'color': '#173177'
  298. }
  299. })
  300. },
  301. 'device_fault': {
  302. 'templateId': '',
  303. 'context': json.dumps({
  304. 'first': {
  305. 'value': u'${title}',
  306. 'color': '#173177'
  307. },
  308. 'keyword1': {
  309. "value": u'${device}',
  310. "color": '#173177'
  311. },
  312. 'keyword2': {
  313. 'value': u'${location}',
  314. 'color': '#173177'
  315. },
  316. 'keyword3': {
  317. 'value': u'${fault}',
  318. 'color': '#173177'
  319. },
  320. 'keyword4': {
  321. 'value': '${notifyTime}',
  322. 'color': '#173177'
  323. },
  324. 'remark': {
  325. 'value': u'感谢您的使用,小客服随时为您服务',
  326. 'color': '#173177'
  327. }
  328. })
  329. },
  330. 'sim_expire_notify': {
  331. 'templateId': '',
  332. 'context': json.dumps({
  333. 'first': {
  334. 'value': u'${title}',
  335. 'color': '#173177'
  336. },
  337. 'keyword1': {
  338. "value": u'${num}',
  339. "color": '#173177'
  340. },
  341. 'keyword2': {
  342. 'value': u'${type}',
  343. 'color': '#173177'
  344. },
  345. 'keyword3': {
  346. 'value': u'${time}',
  347. 'color': '#173177'
  348. },
  349. 'remark': {
  350. 'value': u'感谢您一直以来对我们工作的支持与帮助',
  351. 'color': '#173177'
  352. }
  353. })
  354. },
  355. 'system_alarm_notify': {
  356. 'templateId': '',
  357. 'context': json.dumps({
  358. 'first': {
  359. 'value': u'${title}',
  360. 'color': '#173177'
  361. },
  362. 'keyword1': {
  363. "value": u'${account}',
  364. "color": '#173177'
  365. },
  366. 'keyword2': {
  367. 'value': u'${object}',
  368. 'color': '#173177'
  369. },
  370. 'keyword3': {
  371. 'value': u'${content}',
  372. 'color': '#173177'
  373. },
  374. 'keyword4': {
  375. 'value': u'${time}',
  376. 'color': '#173177'
  377. },
  378. 'remark': {
  379. 'value': u'客户就是上帝,请尽快处理',
  380. 'color': '#173177'
  381. }
  382. })
  383. },
  384. # 'online_notify': {
  385. # 'templateId': '',
  386. # 'context': json.dumps({
  387. # 'first': {
  388. # 'value': u'设备正常上线',
  389. # 'color': '#173177'
  390. # },
  391. # 'keyword1': {
  392. # "value": u'正常上线',
  393. # "color": '#173177'
  394. # },
  395. # 'keyword2': {
  396. # 'value': u'${device}',
  397. # 'color': '#173177'
  398. # },
  399. # 'keyword3': {
  400. # 'value': '${devNo}',
  401. # 'color': '#173177'
  402. # },
  403. # 'keyword4': {
  404. # 'value': '${devType}',
  405. # 'color': '#173177'
  406. # },
  407. # 'keyword5': {
  408. # 'value': '${address}',
  409. # 'color': '#173177'
  410. # },
  411. # 'remark': {
  412. # 'value': u'感谢您的使用,小客服随时为您服务',
  413. # 'color': '#173177'
  414. # }
  415. # })
  416. # }
  417. }
  418. def __repr__(self):
  419. return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
  420. def to_dict(self):
  421. template_id_map = []
  422. for templateName, template in self.templateIdMap.iteritems():
  423. template_id_map.append({
  424. 'key': templateName,
  425. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  426. 'value': template['templateId']
  427. })
  428. for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
  429. if templateName not in self.templateIdMap.keys():
  430. template_id_map.append({
  431. 'key': templateName,
  432. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  433. 'value': ''
  434. })
  435. return {
  436. 'appid': self.appid,
  437. 'secret': self.secret,
  438. 'name': self.name,
  439. 'companyName': self.companyName,
  440. 'templateIdMap': template_id_map
  441. }
  442. @classmethod
  443. def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
  444. __template_id_map = {}
  445. if templateIdMap:
  446. for item in templateIdMap:
  447. if item['key'] not in cls.MESSAGE_TEMPLATE:
  448. continue
  449. __template_id_map[item['key']] = {
  450. 'templateId': item['value'].strip() if item['value'] else '',
  451. 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
  452. }
  453. else:
  454. __template_id_map.update(cls.MESSAGE_TEMPLATE)
  455. return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
  456. companyName = companyName)
  457. class WechatUserManagerApp(EmbeddedApp):
  458. templateIdMap = DictField(verbose_name = u"微信推送消息模版ID字典", default = {})
  459. MESSAGE_TEMPLATE = {
  460. 'feedback_process': {
  461. 'templateId': '',
  462. 'context': json.dumps({
  463. "first": {
  464. "value": u"${title}",
  465. "color": "#173177"
  466. },
  467. "event": {
  468. "value": u"${event}",
  469. "color": "#173177"
  470. },
  471. "finish_time": {
  472. "value": '${finishTime}',
  473. "color": "#173177"
  474. },
  475. "remark": {
  476. "value": u"${remark}",
  477. "color": "#173177"
  478. }
  479. })
  480. },
  481. 'service_start': {
  482. 'templateId': '',
  483. 'context': json.dumps({
  484. 'first': {
  485. 'value': u'${title}',
  486. 'color': '#173177'
  487. },
  488. 'keyword1': {
  489. 'value': u'${service}',
  490. 'color': '#173177'
  491. },
  492. 'keyword2': {
  493. "value": u'${time}',
  494. "color": '#173177'
  495. },
  496. 'remark': {
  497. 'value': u'${remark}',
  498. 'color': '#173177'
  499. }
  500. })
  501. },
  502. 'service_complete': {
  503. 'templateId': '',
  504. 'context': json.dumps({
  505. 'first': {
  506. 'value': u'${title}',
  507. 'color': '#173177'
  508. },
  509. 'keyword1': {
  510. 'value': u'${service}',
  511. 'color': '#173177'
  512. },
  513. 'keyword2': {
  514. "value": u'${finishTime}',
  515. "color": '#173177'
  516. },
  517. 'remark': {
  518. 'value': u'${remark}',
  519. 'color': '#173177'
  520. }
  521. })
  522. },
  523. 'refund_coins': {
  524. 'templateId': '',
  525. 'context': json.dumps({
  526. 'first': {
  527. 'value': u'${title}',
  528. 'color': '#173177'
  529. },
  530. 'keyword1': {
  531. 'value': u'${backCount}',
  532. 'color': '#173177'
  533. },
  534. 'keyword2': {
  535. "value": '${finishTime}',
  536. "color": '#173177'
  537. },
  538. 'remark': {
  539. 'value': u'感谢您的支持!',
  540. 'color': '#173177'
  541. }
  542. })
  543. },
  544. 'device_fault': {
  545. 'templateId': '',
  546. 'context': json.dumps({
  547. 'first': {
  548. 'value': u'${title}',
  549. 'color': '#173177'
  550. },
  551. 'performance': {
  552. "value": u'${fault}',
  553. "color": '#173177'
  554. },
  555. 'time': {
  556. 'value': u'${notifyTime}',
  557. 'color': '#173177'
  558. },
  559. 'remark': {
  560. 'value': u'感谢您的使用,小客服随时为您服务',
  561. 'color': '#173177'
  562. }
  563. })
  564. },
  565. 'less_balance': {
  566. 'templateId': '',
  567. 'context': json.dumps({
  568. 'first': {
  569. 'value': u'${title}',
  570. 'color': '#173177'
  571. },
  572. 'keyword1': {
  573. 'value': '${account}',
  574. 'color': '#173177'
  575. },
  576. 'keyword2': {
  577. "value": '${balance}',
  578. "color": '#173177'
  579. },
  580. 'remark': {
  581. 'value': u'为了不影响您的使用,建议您赶快充值!',
  582. 'color': '#173177'
  583. }
  584. })
  585. },
  586. 'consume_notify': {
  587. 'templateId': '',
  588. 'context': json.dumps({
  589. 'first': {
  590. 'value': u'${title}',
  591. 'color': '#173177'
  592. },
  593. 'keyword1': {
  594. 'value': u'${serviceType}',
  595. 'color': '#173177'
  596. },
  597. 'keyword2': {
  598. 'value': u'${money}',
  599. 'color': '#173177'
  600. },
  601. 'keyword3': {
  602. "value": '${finishTime}',
  603. "color": '#173177'
  604. },
  605. 'remark': {
  606. 'value': u'如果确认不是合法刷卡使用,可以通过解绑卡,让卡不可用。',
  607. 'color': '#173177'
  608. }
  609. })
  610. },
  611. 'service_expired': {
  612. 'templateId': '',
  613. 'context': json.dumps({
  614. 'first': {
  615. 'value': u'${title}',
  616. 'color': '#173177'
  617. },
  618. 'keyword1': {
  619. 'value': u'${department}',
  620. 'color': '#173177'
  621. },
  622. 'keyword2': {
  623. "value": '${expiredTime}',
  624. "color": '#173177'
  625. },
  626. 'remark': {
  627. 'value': u'${remark}', # u'您可以打开公众号的个人中心,选择优惠卡卷,然后选择您即将过期的卡进行续费!',
  628. 'color': '#173177'
  629. }
  630. })
  631. },
  632. 'dev_start': {
  633. 'templateId': '',
  634. 'context': json.dumps({
  635. 'first': {
  636. 'value': u'${title}',
  637. 'color': '#173177'
  638. },
  639. 'keyword1': {
  640. "value": u'${things}',
  641. "color": '#173177'
  642. },
  643. 'keyword2': {
  644. 'value': u'${time}',
  645. 'color': '#173177'
  646. },
  647. 'remark': {
  648. 'value': u'${remark}',
  649. 'color': '#173177'
  650. }
  651. })
  652. },
  653. 'insurance_sub': {
  654. 'templateId': '',
  655. 'context': json.dumps({
  656. 'first': {
  657. 'value': u'${title}',
  658. 'color': '#000000'
  659. },
  660. 'keyword1': {
  661. "value": u'${name}',
  662. "color": '#858585'
  663. },
  664. 'keyword2': {
  665. 'value': u'${category}',
  666. 'color': '#858585'
  667. },
  668. 'keyword3': {
  669. 'value': u'${orderNo}',
  670. 'color': '#858585'
  671. },
  672. 'keyword4': {
  673. 'value': u'${money}',
  674. 'color': '#858585'
  675. },
  676. 'keyword5': {
  677. 'value': u'${validTime}',
  678. 'color': '#858585'
  679. },
  680. 'remark': {
  681. 'value': u'${remark}',
  682. 'color': '#173177'
  683. }
  684. })
  685. },
  686. 'insurance_cancel': {
  687. 'templateId': '',
  688. 'context': json.dumps({
  689. 'first': {
  690. 'value': u'${title}',
  691. 'color': '#858585'
  692. },
  693. 'keyword1': {
  694. "value": u'${orderNo}',
  695. "color": '#858585'
  696. },
  697. 'keyword2': {
  698. 'value': u'${name}',
  699. 'color': '#858585'
  700. },
  701. 'keyword3': {
  702. 'value': u'${user}',
  703. 'color': '#858585'
  704. },
  705. 'keyword4': {
  706. 'value': u'${cancelTime}',
  707. 'color': '#858585'
  708. },
  709. 'keyword5': {
  710. 'value': u'${money}',
  711. 'color': '#858585'
  712. },
  713. 'remark': {
  714. 'value': u'${remark}',
  715. 'color': '#173177'
  716. }
  717. })
  718. },
  719. }
  720. def __repr__(self):
  721. return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
  722. def to_dict(self):
  723. template_id_map = []
  724. for templateName, template in self.templateIdMap.iteritems():
  725. template_id_map.append({
  726. 'key': templateName,
  727. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  728. 'value': template['templateId']
  729. })
  730. for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
  731. if templateName not in self.templateIdMap.keys():
  732. template_id_map.append({
  733. 'key': templateName,
  734. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  735. 'value': ''
  736. })
  737. return {
  738. 'appid': self.appid,
  739. 'secret': self.secret,
  740. 'name': self.name,
  741. 'companyName': self.companyName,
  742. 'templateIdMap': template_id_map
  743. }
  744. @classmethod
  745. def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
  746. __template_id_map = {}
  747. if templateIdMap:
  748. for item in templateIdMap:
  749. if item['key'] not in cls.MESSAGE_TEMPLATE:
  750. continue
  751. __template_id_map[item['key']] = {
  752. 'templateId': item['value'].strip() if item['value'] else '',
  753. 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
  754. }
  755. else:
  756. __template_id_map.update(cls.MESSAGE_TEMPLATE)
  757. return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
  758. companyName = companyName)
  759. class WechatUserSubscribeManagerApp(EmbeddedApp):
  760. templateIdMap = DictField(verbose_name = "微信推送消息模版ID字典", default = {})
  761. MESSAGE_TEMPLATE = {
  762. 'service_start': {
  763. 'templateId': '',
  764. 'context': json.dumps({
  765. 'data': {
  766. 'thing2': {'value': u'${service}'}, # 服务项目
  767. 'time4': {'value': u'${time}'}, # 开始时间
  768. 'thing5': {'value': u'${remark}'}, # 备注
  769. },
  770. })
  771. },
  772. 'service_complete': {
  773. 'templateId': '',
  774. 'context': json.dumps({
  775. 'data': {
  776. 'thing5': {'value': u'${service}'}, # 服务名称
  777. 'thing6': {'value': u'${remark}'}, # 温馨提示
  778. 'time3': {'value': u'${finishTime}'}, # 完成时间
  779. },
  780. })
  781. }
  782. }
  783. def __repr__(self):
  784. return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
  785. def to_dict(self):
  786. template_id_map = []
  787. for templateName, template in self.templateIdMap.iteritems():
  788. template_id_map.append({
  789. 'key': templateName,
  790. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  791. 'value': template['templateId']
  792. })
  793. for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
  794. if templateName not in self.templateIdMap.keys():
  795. template_id_map.append({
  796. 'key': templateName,
  797. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  798. 'value': ''
  799. })
  800. return {
  801. 'appid': self.appid,
  802. 'secret': self.secret,
  803. 'name': self.name,
  804. 'companyName': self.companyName,
  805. 'templateIdMap': template_id_map
  806. }
  807. @classmethod
  808. def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
  809. __template_id_map = {}
  810. if templateIdMap:
  811. for item in templateIdMap:
  812. if item['key'] not in cls.MESSAGE_TEMPLATE:
  813. continue
  814. __template_id_map[item['key']] = {
  815. 'templateId': item['value'].strip() if item['value'] else '',
  816. 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
  817. }
  818. else:
  819. __template_id_map.update(cls.MESSAGE_TEMPLATE)
  820. return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
  821. companyName = companyName)
  822. class WechatDealerSubscribeManagerApp(EmbeddedApp):
  823. templateIdMap = DictField(verbose_name = "微信推送消息模版ID字典", default = {})
  824. MESSAGE_TEMPLATE = {
  825. 'service_start': {
  826. 'templateId': '',
  827. 'context': json.dumps({
  828. 'data': {
  829. 'character_string1': {'value': u'${orderNo}'}, # 订单号
  830. 'thing2': {'value': u'${majorDeviceType}'}, # 服务项目
  831. 'time4': {'value': u'${time}'}, # 开始时间
  832. 'thing5': {'value': u'${remarks}'}, # 备注
  833. },
  834. })
  835. },
  836. }
  837. def __repr__(self):
  838. return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
  839. def to_dict(self):
  840. template_id_map = []
  841. for templateName, template in self.templateIdMap.iteritems():
  842. template_id_map.append({
  843. 'key': templateName,
  844. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  845. 'value': template['templateId']
  846. })
  847. for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
  848. if templateName not in self.templateIdMap.keys():
  849. template_id_map.append({
  850. 'key': templateName,
  851. 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
  852. 'value': ''
  853. })
  854. return {
  855. 'appid': self.appid,
  856. 'secret': self.secret,
  857. 'name': self.name,
  858. 'companyName': self.companyName,
  859. 'templateIdMap': template_id_map
  860. }
  861. @classmethod
  862. def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
  863. __template_id_map = {}
  864. if templateIdMap:
  865. for item in templateIdMap:
  866. if item['key'] not in cls.MESSAGE_TEMPLATE:
  867. continue
  868. __template_id_map[item['key']] = {
  869. 'templateId': item['value'].strip() if item['value'] else '',
  870. 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
  871. }
  872. else:
  873. __template_id_map.update(cls.MESSAGE_TEMPLATE)
  874. return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
  875. companyName = companyName)
  876. def get_sub_template_id_list(self):
  877. if not self.templateIdMap:
  878. return []
  879. else:
  880. try:
  881. return map(lambda _: _['templateId'], self.templateIdMap.values())
  882. except Exception as e:
  883. logger.info('error e=<{}>'.format(e))
  884. return []
  885. class JDAuthApp(EmbeddedApp):
  886. def __repr__(self): return '<JDAuthApp appid=%s>' % (self.appid,)
  887. def to_dict(self):
  888. return {
  889. 'appid': self.appid,
  890. 'secret': self.secret,
  891. 'name': self.name,
  892. 'companyName': self.companyName
  893. }
  894. class MerchantAgent(Searchable):
  895. """
  896. 将商户号 和 代理商账号绑定起来 这样方便 代理商收取手续费的时候 直接从相应的网关获取
  897. """
  898. agentNo = StringField(verbose_name=u"商户号的服务商商户号")
  899. accessKey = StringField(verbose_name=u"accessKey", default=None)
  900. secretKey = StringField(verbose_name=u"secretKey", default=None)
  901. dateTimeAdded = DateTimeField(verbose_name=u"添加进来的时间")
  902. remarks = StringField(verbose_name=u"备注", default=u"")
  903. agentSystemId = StringField(verbose_name=u"服务商的systemId(京东聚合老系统需要)", default=None)
  904. def __str__(self):
  905. return self.agentNo
  906. @classmethod
  907. def get_agent_id(cls, agentSystemId):
  908. merchantAgent = cls.objects.filter(agentSystemId=agentSystemId).first() or cls()
  909. return merchantAgent.agentId
  910. @classmethod
  911. def default_merchant_agent(cls):
  912. wflSystemId = os.environ.get("JD_SYSTEM_ID") or "112122276PSP"
  913. return cls.objects.filter(agentSystemId=wflSystemId).first()
  914. class PayAppBase(Searchable):
  915. meta = {
  916. 'abstract': True
  917. }
  918. class Function(object):
  919. PAY = 'pay'
  920. WITHDRAW = 'withdraw'
  921. companyName = StringField(verbose_name = u'公司名称', required = True, default = '')
  922. appName = StringField(verbose_name = u'应用名称', required = True, default = u'自助服务')
  923. remarks = StringField(verbose_name = u'备注', default = '')
  924. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加时间')
  925. dateTimeUpdated = DateTimeField(verbose_name = '修改时间')
  926. inhouse = BooleanField(verbose_name = u'是否平台商户', default = False)
  927. debug = BooleanField(verbose_name = u'是否测试账号', default = False)
  928. functions = ListField(verbose_name = u'支持功能列表', default = [Function.PAY])
  929. @property
  930. def pay_app_type(self):
  931. raise AttributeError('no pay_app_type attribute')
  932. @property
  933. def occupantId(self):
  934. return getattr(self, '__occupant_id__', '')
  935. @occupantId.setter
  936. def occupantId(self, occupant_id):
  937. self.__occupant_id__ = occupant_id
  938. @property
  939. def occupant(self):
  940. _obj = getattr(self, '__occupant__', None)
  941. if not _obj:
  942. _obj = ROLE.from_role_id(self.role, self.occupantId)
  943. self.__occupant__ = _obj
  944. return _obj
  945. @occupant.setter
  946. def occupant(self, occupant):
  947. self.__occupant__ = occupant
  948. self.role = occupant.role
  949. @property
  950. def role(self):
  951. return self.__role__
  952. @role.setter
  953. def role(self, role):
  954. self.__role__ = role
  955. @property
  956. def valid(self):
  957. if hasattr(self, '__valid__'):
  958. return getattr(self, '__valid__')
  959. else:
  960. if not hasattr(self, '__valid_check__'):
  961. raise AttributeError('no __valid_check__ attribute')
  962. else:
  963. return getattr(self, '__valid_check__')
  964. @valid.setter
  965. def valid(self, value):
  966. self.__valid__ = value
  967. @property
  968. def enable(self):
  969. # type: ()->bool
  970. return getattr(self, '__enable__', True)
  971. @enable.setter
  972. def enable(self, value):
  973. # type: (bool)->None
  974. self.__enable__ = value
  975. @property
  976. def __gateway_key__(self):
  977. raise AttributeError('no __gateway_key__ attribute')
  978. @property
  979. def __source_key__(self):
  980. raise AttributeError('no __source_key__ attribute')
  981. @classmethod
  982. def get_collection(cls):
  983. return cls._get_collection()
  984. def to_dict(self):
  985. raise NotImplementedError('must implement to_dict method')
  986. @classmethod
  987. def list(cls, page_index, page_size, search_key):
  988. cursor = cls.objects().search(search_key).order_by('-dateTimeAdded')
  989. total = cursor.count()
  990. items = cursor.paginate(page_index, page_size)
  991. return total, [_.to_dict(True) for _ in items]
  992. @classmethod
  993. def __from_gateway_key__(cls, occupant_id, tokens):
  994. # type: (str, List)->cast(PayAppBase)
  995. raise NotImplementedError('must implement from_gateway_key class method')
  996. def new_gateway(self, app_platform_type):
  997. raise NotImplementedError('must implement new_gateway')
  998. def new_withdraw_gateway(self, is_ledger = True, gateway_version = None):
  999. raise NotImplementedError('must implement new_withdraw_gateway')
  1000. @property
  1001. def split_id(self):
  1002. raise NotImplementedError('must implement split_id')
  1003. def refund_bill_split_list(self, partition_map):
  1004. raise NotImplementedError('must implement refund_bill_split_list')
  1005. def bill_split_rule(self, partition_map):
  1006. raise NotImplementedError('must implement refund_bill_split_list')
  1007. class JosShopInfo(EmbeddedDocument):
  1008. """
  1009. 京东云支付的相关信息
  1010. 其中门店ID很关键
  1011. """
  1012. # 品牌ID 查看方式 品牌管理 -> 品牌认证 -> 品牌ID
  1013. brandId = StringField(verbose_name = u"京东引流 品牌ID", default = "13499")
  1014. # 企业BID 查看方式 品牌管理 -> 企业认证 -> 企业BID
  1015. bizId = StringField(verbose_name = u"京东引流 企业BID", default = "100000000006317")
  1016. # 外部门店ID 重要
  1017. # 这个门店的编号直接影响用户的欠款去向 千万注意
  1018. # jos后台的逻辑是 通过 exStoreId 查找到
  1019. # 绑定的京东商户, 通过京东商户找到绑定的钱包 最后用户付款的钱直接到对应钱包里面
  1020. exStoreId = StringField(verbose_name = u"京东引流的外部门店ID,需要web端操作绑定", required = True)
  1021. # 和门店相关联的商品的ID 商品发布后一定需要关联门店,可以多个门店关联一个商品 也可以一个商品关联多个门店
  1022. exSkuId = StringField(verbose_name = u"京东引流的外部商品ID,需要web端操作绑定", default = "100001")
  1023. brandName = StringField(verbose_name = u"品牌名称, 需要和品牌ID对应", default = u"微付乐")
  1024. tradeName = StringField(verbose_name = u"商品名称,需要和商品ID对应", default = u"测试商品")
  1025. class JDAggrePayApp(PayAppBase):
  1026. merchant_no = StringField(verbose_name = u'商户号', unique = True, required = True, null = False)
  1027. desKey = StringField(verbose_name = u'加密秘钥', null = False)
  1028. saltMd5Key = StringField(verbose_name = u'签名盐', null = False)
  1029. systemId = StringField(verbose_name = u'商户代理商ID', null = False)
  1030. boundAppId = StringField(verbose_name = u'绑定的APPID', null = None)
  1031. boundSecret = StringField(verbose_name = u'绑定的SECRET', default = None)
  1032. wxSubMchId = StringField(verbose_name = u'京东服务商下微信子商户ID', default = None)
  1033. regEmail = StringField(verbose_name = u'京东登录用户名', required = True)
  1034. # jos支付所需要的app
  1035. josPayApp = LazyReferenceField(verbose_name = u"JOS支付的app", document_type = "self")
  1036. shopInfo = EmbeddedDocumentField(verbose_name = u"商户所绑定的门店信息 目前仅用于JOS京东引流", document_type = JosShopInfo)
  1037. meta = {
  1038. 'indexes': [
  1039. {
  1040. 'fields': ['merchant_no'], 'unique': True
  1041. },
  1042. {
  1043. 'fields': ['wxSubMchId']
  1044. }
  1045. ],
  1046. 'collection': 'jdaggre_pay_app',
  1047. 'db_alias': 'default'
  1048. }
  1049. def __repr__(self):
  1050. return '<JDAggrePayApp merchant_no=%s>' % (self.merchant_no,)
  1051. @property
  1052. def __valid_check__(self):
  1053. return bool(self.merchant_no) and bool(self.regEmail)
  1054. @property
  1055. def pay_app_type(self):
  1056. return PayAppType.JD_AGGR
  1057. @property
  1058. def __gateway_key__(self):
  1059. _ = [
  1060. self.occupantId,
  1061. self.merchant_no,
  1062. self.occupant.role
  1063. ]
  1064. return APP_KEY_DELIMITER.join(_)
  1065. @classmethod
  1066. def get_null_app(cls):
  1067. app = cls(merchant_no = '')
  1068. app.enable = False
  1069. app.valid = True
  1070. return app
  1071. @classmethod
  1072. def __from_gateway_key__(cls, occupant_id, tokens):
  1073. # type: (str, List)->cast(PayAppBase)
  1074. merchant_no = tokens[0]
  1075. app = cls.objects(merchant_no = merchant_no).first() # type: JDAggrePayApp
  1076. if not app:
  1077. raise UserServerException(u'系统配置错误,请联系平台客服')
  1078. app.occupantId = occupant_id
  1079. try:
  1080. app.role = tokens[1]
  1081. except:
  1082. app.role = ROLE.agent
  1083. return app
  1084. def new_gateway(self, app_platform_type):
  1085. from apps.web.core.payment.jdaggre import JDAggrePaymentGateway
  1086. return JDAggrePaymentGateway(self, app_platform_type)
  1087. def to_dict(self, shadow = True):
  1088. return {
  1089. 'id': str(self.id),
  1090. 'merchant_no': self.merchant_no,
  1091. 'companyName': self.companyName,
  1092. 'appName': self.appName,
  1093. 'remarks': self.remarks,
  1094. 'type': self.pay_app_type,
  1095. 'regEmail': self.regEmail
  1096. }
  1097. @classmethod
  1098. def get_or_create_by_merchant(cls, source):
  1099. # type: (MerchantSourceInfo)->JDAggrePayApp
  1100. merchantAgent = source.merchantAgent.fetch()
  1101. app = cls.objects.filter(merchant_no = source.merchantNo).first() # type: JDAggrePayApp
  1102. if not app:
  1103. app = cls(merchant_no = source.merchantNo,
  1104. desKey = source.desKey,
  1105. saltMd5Key = source.mdKey,
  1106. systemId = merchantAgent.agentSystemId,
  1107. regEmail = source.regEmail,
  1108. companyName = source.blicCompanyName)
  1109. else:
  1110. app.desKey = source.desKey
  1111. app.saltMd5Key = source.mdKey
  1112. app.systemId = merchantAgent.agentSystemId
  1113. app.companyName = source.blicCompanyName
  1114. app.regEmail = source.regEmail
  1115. return app.save()
  1116. @property
  1117. def split_id(self):
  1118. return self.regEmail
  1119. def bill_split_rule(self, partition_map):
  1120. """
  1121. 京东支付分账规则:不带拉起支付商户的商户注册邮箱和金额
  1122. :return:
  1123. """
  1124. partitions = list(flatten(partition_map.values()))
  1125. rv = {}
  1126. owner_merchant_id = None
  1127. for partition in partitions:
  1128. if partition['role'] == PARTITION_ROLE.OWNER:
  1129. owner_merchant_id = partition['merchantId']
  1130. else:
  1131. if RMB(partition['money']) > RMB(0):
  1132. if partition['merchantId'] in rv:
  1133. item = rv[partition['merchantId']]
  1134. item['splitBillAmount'] = str(RMB(item['splitBillAmount']) + RMB(partition['money']))
  1135. else:
  1136. rv[partition['merchantId']] = {
  1137. 'splitBillMerchantEmail': partition['merchantId'],
  1138. 'splitBillAmount': str(RMB(partition['money']))
  1139. }
  1140. rv.pop(owner_merchant_id, None)
  1141. return 'FIXED', 'RECEIVER', rv.values()
  1142. def refund_bill_split_list(self, partition_map):
  1143. """
  1144. 京东退款分账规则:
  1145. 1、即使退款为0, 也必须带上拉起支付的商户号
  1146. 2、所有参与支付分账的商户, 即使金额为0, 也必须带上
  1147. :return:
  1148. """
  1149. ownerPartition = partition_map.get(PARTITION_ROLE.OWNER)[0]
  1150. if 'merchantId' not in ownerPartition:
  1151. return None
  1152. owner_merchant_id = ownerPartition['merchantId']
  1153. partitions = list(flatten(partition_map.values()))
  1154. items = {}
  1155. for partition in partitions:
  1156. if 'merchantId' not in partition:
  1157. continue
  1158. if partition['merchantId'] in items:
  1159. item = items[partition['merchantId']]
  1160. item['splitBillAmount'] = str(RMB(item['splitBillAmount']) + abs(RMB(partition['money'])))
  1161. else:
  1162. items[partition['merchantId']] = {
  1163. 'splitBillMerchantEmail': partition['merchantId'],
  1164. 'splitBillAmount': str(abs(RMB(partition['money'])))
  1165. }
  1166. ownerItem = items.pop(owner_merchant_id)
  1167. if len(items.values()) == 0:
  1168. return None
  1169. else:
  1170. rv = items.values()
  1171. rv.append(ownerItem)
  1172. return rv
  1173. class SaobeiPayApp(PayAppBase):
  1174. merchant_no = StringField(verbose_name = u'merchant no', required = True, null = False, max_length = 15)
  1175. terminal_id = StringField(verbose_name = u'terminal id', required = True, null = False, max_length = 8)
  1176. access_token = StringField(verbose_name = u'access token', required = True, null = False, max_length = 32)
  1177. meta = {
  1178. 'indexes': [
  1179. {
  1180. 'fields': ['merchant_no', 'terminal_id'], 'unique': True
  1181. },
  1182. ],
  1183. 'collection': 'saobei_pay_app',
  1184. 'db_alias': 'default'
  1185. }
  1186. def __repr__(self):
  1187. return '<SaobeiPayApp merchant_no=%s,terminal_id=%s>' % (self.merchant_no, self.terminal_id)
  1188. @property
  1189. def __valid_check__(self):
  1190. return bool(self.merchant_no and self.terminal_id and self.access_token)
  1191. @property
  1192. def pay_app_type(self):
  1193. return PayAppType.SAOBEI
  1194. @property
  1195. def __gateway_key__(self):
  1196. _ = [
  1197. self.occupantId,
  1198. self.merchant_no,
  1199. self.terminal_id,
  1200. self.occupant.role
  1201. ]
  1202. return APP_KEY_DELIMITER.join(_)
  1203. def to_dict(self, shadow = True):
  1204. return {
  1205. 'id': str(self.id),
  1206. 'merchant_no': self.merchant_no,
  1207. 'terminal_id': self.terminal_id,
  1208. 'access_token': encrypt_display(self.access_token) if shadow else self.access_token,
  1209. 'companyName': self.companyName,
  1210. 'appName': self.appName,
  1211. 'remarks': self.remarks,
  1212. 'type': self.pay_app_type
  1213. }
  1214. @classmethod
  1215. def get_null_app(cls):
  1216. app = cls(merchant_no = '', terminal_id = '')
  1217. app.enable = False
  1218. app.valid = True
  1219. return app
  1220. @classmethod
  1221. def __from_gateway_key__(cls, occupant_id, tokens):
  1222. # type: (str, List)->cast(PayAppBase)
  1223. merchant_no, terminal_id = tokens[0], tokens[1]
  1224. app = cls.objects(merchant_no = merchant_no, terminal_id = terminal_id).first() # type: SaobeiPayApp
  1225. if not app:
  1226. raise UserServerException(u'系统配置错误,请联系平台客服')
  1227. app.occupantId = occupant_id
  1228. try:
  1229. app.role = tokens[2]
  1230. except:
  1231. app.role = ROLE.agent
  1232. return app
  1233. def new_gateway(self, app_platform_type):
  1234. from apps.web.core.payment.saobei import SaobeiPaymentGateway
  1235. return SaobeiPaymentGateway(self, app_platform_type)
  1236. class DlbPayApp(PayAppBase):
  1237. merchant_no = StringField(verbose_name = u'merchant_no', required = True, null = False, max_length = 32)
  1238. shop_no = StringField(verbose_name = u'shop_no', required = True, null = False, max_length = 32)
  1239. machine_no = StringField(verbose_name = u'machine_no', required = True, null = False, max_length = 32)
  1240. access_key = StringField(verbose_name = u'machine_no', required = True, null = False, max_length = 64)
  1241. secret_key = StringField(verbose_name = u'machine_no', required = True, null = False, max_length = 64)
  1242. meta = {
  1243. 'indexes': [
  1244. {
  1245. 'fields': ['merchant_no', 'shop_no'], 'unique': True
  1246. },
  1247. ],
  1248. 'collection': 'dlb_pay_app',
  1249. 'db_alias': 'default'
  1250. }
  1251. def __repr__(self):
  1252. return '<DlbPayApp merchant_no=%s, shop_no=%s>' % (self.merchant_no, self.shop_no,)
  1253. @property
  1254. def __valid_check__(self):
  1255. return bool(self.merchant_no and self.shop_no)
  1256. @property
  1257. def pay_app_type(self):
  1258. return PayAppType.DLB
  1259. @property
  1260. def __gateway_key__(self):
  1261. _ = [
  1262. self.occupantId,
  1263. self.merchant_no,
  1264. self.shop_no,
  1265. self.occupant.role
  1266. ]
  1267. return APP_KEY_DELIMITER.join(_)
  1268. def to_dict(self, shadow = True):
  1269. return {
  1270. 'id': str(self.id),
  1271. 'merchant_no': self.merchant_no,
  1272. 'shop_no': self.shop_no,
  1273. 'machine_no': self.machine_no,
  1274. 'companyName': self.companyName,
  1275. 'appName': self.appName,
  1276. 'remarks': self.remarks,
  1277. 'type': self.pay_app_type
  1278. }
  1279. @classmethod
  1280. def get_null_app(cls):
  1281. app = cls(merchant_no = '', shop_no = '')
  1282. app.enable = False
  1283. app.valid = True
  1284. return app
  1285. @classmethod
  1286. def __from_gateway_key__(cls, occupant_id, tokens):
  1287. # type: (str, List)->cast(PayAppBase)
  1288. merchant_no, shop_no = tokens[0], tokens[1]
  1289. app = cls.objects(merchant_no = merchant_no, shop_no = shop_no).first() # type: DlbPayApp
  1290. if not app:
  1291. raise UserServerException(u'系统配置错误,请联系平台客服')
  1292. app.occupantId = occupant_id
  1293. try:
  1294. app.role = tokens[2]
  1295. except:
  1296. app.role = ROLE.agent
  1297. return app
  1298. def new_gateway(self, app_platform_type):
  1299. from apps.web.core.payment.dlb import DlbPaymentGateway
  1300. return DlbPaymentGateway(self, app_platform_type)
  1301. class YsPayApp(PayAppBase):
  1302. channel_id = StringField(verbose_name = u'channel_id', required = True, null = False, max_length = 32)
  1303. mer_id = StringField(verbose_name = u'mer_id', required = True, null = False, max_length = 32)
  1304. term_id = StringField(verbose_name = u'term_id', required = True, null = False, max_length = 32)
  1305. work_key = StringField(verbose_name = u'work_key', required = True, null = False, max_length = 64)
  1306. meta = {
  1307. 'indexes': [
  1308. {
  1309. 'fields': ['channel_id', 'mer_id', 'term_id'], 'unique': True
  1310. },
  1311. ],
  1312. 'collection': 'ys_pay_app',
  1313. 'db_alias': 'default'
  1314. }
  1315. def __repr__(self):
  1316. return '<YsPayApp channel_id=%s, mer_id=%s>' % (self.channel_id, self.mer_id)
  1317. @property
  1318. def __valid_check__(self):
  1319. return bool(self.channel_id and self.mer_id and self.term_id)
  1320. @property
  1321. def pay_app_type(self):
  1322. return PayAppType.YS
  1323. @property
  1324. def __gateway_key__(self):
  1325. _ = [
  1326. self.occupantId,
  1327. self.channel_id,
  1328. self.mer_id,
  1329. self.term_id,
  1330. self.occupant.role
  1331. ]
  1332. return APP_KEY_DELIMITER.join(_)
  1333. def to_dict(self, shadow = True):
  1334. return {
  1335. 'id': str(self.id),
  1336. 'channel_id': self.channel_id,
  1337. 'mer_id': self.mer_id,
  1338. 'term_id': self.term_id,
  1339. 'companyName': self.companyName,
  1340. 'appName': self.appName,
  1341. 'remarks': self.remarks,
  1342. 'type': self.pay_app_type
  1343. }
  1344. @classmethod
  1345. def get_null_app(cls):
  1346. app = cls(channel_id = '', mer_id = '')
  1347. app.enable = False
  1348. app.valid = True
  1349. return app
  1350. @classmethod
  1351. def __from_gateway_key__(cls, occupant_id, tokens):
  1352. channel_id, mer_id, term_id = tokens[0], tokens[1], tokens[2]
  1353. app = cls.objects(channel_id = channel_id, mer_id = mer_id, term_id = term_id).first() # type: YsPayApp
  1354. if not app:
  1355. raise UserServerException(u'系统配置错误,请联系平台客服')
  1356. app.occupantId = occupant_id
  1357. try:
  1358. app.role = tokens[3]
  1359. except:
  1360. app.role = ROLE.agent
  1361. return app
  1362. def new_gateway(self, app_platform_type):
  1363. from apps.web.core.payment.ys import YsPaymentGateway
  1364. return YsPaymentGateway(self, app_platform_type)
  1365. class WechatPayApp(PayAppBase):
  1366. """
  1367. 对于V1接口, 需要sslKey(证书私钥)和sslCert(证书字符串)来调用接口, 使用apikey来加解密信息
  1368. 对于V3接口, 需要sslKey(证书私钥)和app_serial_number(证书序列号)来调用接口, 使用apikey_v3来加解密信息
  1369. """
  1370. appid = StringField(verbose_name = u'应用ID', required = True, null = False)
  1371. secret = StringField(verbose_name = u"secretId", required = True, null = False, max_length = 32)
  1372. mchid = StringField(verbose_name = u'mchid', null = False)
  1373. sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '')
  1374. apikey = StringField(verbose_name = u'apikey', null = False)
  1375. sslCert = StringField(verbose_name = u'PERM证书内容', default = '')
  1376. apikey_v3 = StringField(verbose_name = u'v3 api key. 加解密V3接口敏感信息', default = None)
  1377. app_serial_number = StringField(verbose_name = u'商户证书序列号', default = None)
  1378. platform_certificates = ListField(verbose_name = u'平台证书列表', default = None)
  1379. # 暂时保留
  1380. sslcert_path = StringField(verbose_name = '证书pem格式', default = None)
  1381. sslkey_path = StringField(verbose_name = '证书密钥pem格式', default = None)
  1382. rawAppId = StringField(verbose_name = u'原始ID', default = '')
  1383. rootca_path = StringField(verbose_name = u'root证书路径', default = '')
  1384. rootCA = StringField(verbose_name = u'root证书', default = '')
  1385. withdrawV3 = BooleanField(verbose_name = u'是否使用V3接口对微信转账', default = False)
  1386. manual_withdraw = BooleanField(verbose_name = u'是否手动提现', default = False)
  1387. functions = ListField(verbose_name = u'支持功能列表', default = [PayAppBase.Function.PAY, PayAppBase.Function.WITHDRAW])
  1388. search_fields = ('appid', 'mchid', 'name', 'remarks')
  1389. meta = {
  1390. 'indexes': [
  1391. {
  1392. 'fields': ['appid', 'mchid'], 'unique': True
  1393. },
  1394. ],
  1395. 'collection': 'wechat_pay_app',
  1396. 'db_alias': 'default'
  1397. }
  1398. def __repr__(self):
  1399. return '<WechatPayApp appid={}>'.format(self.appid)
  1400. @property
  1401. def __valid_check__(self):
  1402. return bool(self.appid and self.secret and self.mchid and self.ssl_key and self.ssl_cert and self.apikey)
  1403. @property
  1404. def pay_app_type(self):
  1405. return PayAppType.WECHAT
  1406. @property
  1407. def __gateway_key__(self):
  1408. _ = [
  1409. self.occupantId,
  1410. self.appid,
  1411. self.mchid,
  1412. self.occupant.role
  1413. ]
  1414. return APP_KEY_DELIMITER.join(_)
  1415. @property
  1416. def __source_key__(self):
  1417. return APP_KEY_DELIMITER.join([
  1418. self.occupantId,
  1419. self.mchid
  1420. ])
  1421. def to_dict(self, shadow = False):
  1422. return {
  1423. 'id': str(self.id),
  1424. 'appid': self.appid,
  1425. 'mchid': self.mchid,
  1426. 'secret': encrypt_display(self.secret) if shadow else self.secret,
  1427. 'apikey': encrypt_display(self.apikey) if shadow else self.apikey,
  1428. 'ssl_key': base64.b64encode(self.ssl_key),
  1429. 'ssl_cert': base64.b64encode(self.ssl_cert),
  1430. 'manual_withdraw': self.manual_withdraw,
  1431. 'companyName': self.companyName,
  1432. 'appName': self.appName,
  1433. 'remarks': self.remarks
  1434. }
  1435. @classmethod
  1436. def get_null_app(cls):
  1437. app = cls(appid = '',
  1438. secret = '',
  1439. mchid = '',
  1440. apikey = '',
  1441. sslKey = '',
  1442. sslCert = '',
  1443. sslcert_path = '',
  1444. sslkey_path = '',
  1445. manual_withdraw = False)
  1446. app.enable = False
  1447. app.valid = True
  1448. return app
  1449. @classmethod
  1450. def __from_gateway_key__(cls, occupant_id, tokens):
  1451. # type: (str, List)->cast(PayAppBase)
  1452. appid, mchid = tokens[0], tokens[1]
  1453. app = cls.objects(appid = appid, mchid = mchid).first() # type: WechatPayApp
  1454. if not app:
  1455. raise UserServerException(u'系统配置错误,请联系平台客服')
  1456. app.occupantId = occupant_id
  1457. try:
  1458. app.role = tokens[2]
  1459. except:
  1460. app.role = ROLE.agent
  1461. return app
  1462. @classmethod
  1463. def __from_source_key__(cls, occupant_id, tokens):
  1464. # type: (str, List)->cast(PayAppBase)
  1465. # TODO 查找最新加的商户号. 需要增加商户号管理功能,可以禁止,设置active等功能
  1466. app = cls.objects(mchid = tokens[0]).order_by('-dateTimeAdded').first()
  1467. if not app:
  1468. raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服')
  1469. app.occupantId = occupant_id
  1470. return app
  1471. def new_gateway(self, app_platform_type):
  1472. from apps.web.core.payment.wechat import WechatPaymentGateway
  1473. return WechatPaymentGateway(self)
  1474. def new_withdraw_gateway(self, is_ledger = True, gateway_version = None):
  1475. from apps.web.core.payment.wechat import WechatWithdrawGateway
  1476. if not self.enable:
  1477. is_ledger = False
  1478. if self.withdrawV3 and self.apikey_v3:
  1479. gateway_version = 'v3'
  1480. else:
  1481. gateway_version = 'v1'
  1482. return WechatWithdrawGateway(self, gateway_version = gateway_version, is_ledger = is_ledger)
  1483. @property
  1484. def ssl_cert(self):
  1485. if not hasattr(self, '__ssl_cert__'):
  1486. try:
  1487. if self.sslCert.startswith('-----BEGIN'):
  1488. setattr(self, '__ssl_cert__', self.sslCert)
  1489. elif self.sslcert_path.startswith('-----BEGIN'):
  1490. setattr(self, '__ssl_cert__', self.sslcert_path)
  1491. else:
  1492. with open(self.sslcert_path) as fp:
  1493. setattr(self, '__ssl_cert__', fp.read())
  1494. except Exception as e:
  1495. logger.error('{} ssl_cert exception = {}'.format(repr(self), e.message))
  1496. setattr(self, '__ssl_cert__', '')
  1497. return getattr(self, '__ssl_cert__')
  1498. @property
  1499. def ssl_key(self):
  1500. if not hasattr(self, '__ssl_key__'):
  1501. try:
  1502. if self.sslKey.startswith('-----BEGIN'):
  1503. setattr(self, '__ssl_key__', self.sslKey)
  1504. elif self.sslkey_path.startswith('-----BEGIN'):
  1505. setattr(self, '__ssl_key__', self.sslkey_path)
  1506. else:
  1507. with open(self.sslkey_path) as fp:
  1508. setattr(self, '__ssl_key__', fp.read())
  1509. except Exception as e:
  1510. logger.error('{} ssl_key exception = {}'.format(repr(self), e.message))
  1511. setattr(self, '__ssl_key__', '')
  1512. return getattr(self, '__ssl_key__')
  1513. @update_certificates.connect
  1514. def update_v3_certificates(sender, mchid, cert_str_list):
  1515. WechatPayApp.objects(mchid = mchid).update(platform_certificates = cert_str_list)
  1516. class AliApp(PayAppBase):
  1517. appid = StringField(verbose_name = u'应用ID', required = True, null = False)
  1518. # 参数加密秘钥. 一般不加密
  1519. aesEncryptKey = StringField(verbose_name = u'加密秘钥', default = '')
  1520. # 公钥模式
  1521. app_private_key_path = StringField(verbose_name = u'支付宝应用私钥路径', default = '')
  1522. appPrivateKey = StringField(verbose_name = u'支付宝应用私钥. 只有测试情况下才能使用字符串', default = '')
  1523. public_key_path = StringField(verbose_name = u'支付宝账户公钥路径', default = '')
  1524. alipayPublicKey = StringField(verbose_name = u'文本形式支付宝账户公钥', default = '')
  1525. # 证书模式
  1526. app_publickey_cert_path = StringField(verbose_name = u'支付宝应用公共证书路径', default = '')
  1527. appPublicKeyCert = StringField(verbose_name = u'支付宝应用公共证书', default = '')
  1528. publickey_cert_path = StringField(verbose_name = u'支付宝公共证书路径', default = '')
  1529. publicKeyCert = StringField(verbose_name = u'支付宝公共证书', default = '')
  1530. root_cert_path = StringField(verbose_name = u'支付宝根证书路径', default = '')
  1531. rootCert = StringField(verbose_name = u'支付宝根证书', default = '')
  1532. signKeyType = StringField(verbose_name = u'签名方式(normal,cert)', default = 'normal')
  1533. # 中间字段
  1534. alipayPublicKeyContent = StringField(verbose_name = u'支付宝公钥文本', default = '')
  1535. appPublicKeyContent = StringField(verbose_name = u'应用公钥文本', default = '')
  1536. search_fields = ('appid', 'name', 'remarks')
  1537. meta = {
  1538. 'indexes': [
  1539. {
  1540. 'fields': ['appid'], 'unique': True
  1541. },
  1542. ],
  1543. 'collection': 'ali_pay_app',
  1544. 'db_alias': 'default'
  1545. }
  1546. def __repr__(self):
  1547. return '<AliApp appid={}>'.format(self.appid)
  1548. @property
  1549. def __valid_check__(self):
  1550. if not self.appid:
  1551. return False
  1552. if self.signKeyType == 'normal':
  1553. return bool(self.public_key_string and self.app_private_key_string)
  1554. if self.signKeyType == 'cert':
  1555. return bool(self.app_private_key_string and
  1556. self.public_key_cert_string and
  1557. self.root_cert_string and
  1558. self.app_public_key_cert_string)
  1559. return False
  1560. @property
  1561. def aes_encrypt_key(self):
  1562. return self.aesEncryptKey
  1563. @property
  1564. def app_private_key_string(self):
  1565. if not hasattr(self, '__app_private_key_string__'):
  1566. try:
  1567. if self.appPrivateKey.startswith('-----BEGIN'):
  1568. setattr(self, '__app_private_key_string__', self.appPrivateKey)
  1569. elif self.app_private_key_path and self.app_private_key_path.startswith('-----BEGIN'):
  1570. setattr(self, '__app_private_key_string__', self.app_private_key_path)
  1571. else:
  1572. with open(self.app_private_key_path) as fp:
  1573. setattr(self, '__app_private_key_string__', fp.read())
  1574. except Exception as e:
  1575. logger.error('{} app_private_key_string exception = {}'.format(repr(self), e.message))
  1576. setattr(self, '__app_private_key_string__', '')
  1577. return getattr(self, '__app_private_key_string__')
  1578. @property
  1579. def public_key_string(self):
  1580. if not hasattr(self, '__public_key_string__'):
  1581. try:
  1582. if self.alipayPublicKey.startswith('-----BEGIN'):
  1583. setattr(self, '__public_key_string__', self.alipayPublicKey)
  1584. elif self.public_key_path.startswith('-----BEGIN'):
  1585. setattr(self, '__public_key_string__', self.public_key_path)
  1586. else:
  1587. with open(self.public_key_path) as fp:
  1588. setattr(self, '__public_key_string__', fp.read())
  1589. except Exception as e:
  1590. logger.error('{} public_key_string exception = {}'.format(repr(self), e.message))
  1591. setattr(self, '__public_key_string__', '')
  1592. return getattr(self, '__public_key_string__')
  1593. @property
  1594. def app_public_key_cert_string(self):
  1595. if not hasattr(self, '__app_public_key_cert_string__'):
  1596. try:
  1597. if self.appPublicKeyCert.startswith('-----BEGIN'):
  1598. setattr(self, '__app_public_key_cert_string__', self.appPublicKeyCert)
  1599. else:
  1600. with open(self.appPublicKeyCert) as fp:
  1601. setattr(self, '__app_public_key_cert_string__', fp.read())
  1602. except Exception as e:
  1603. logger.error('{} app_public_key_cert_string exception = {}'.format(repr(self), e.message))
  1604. setattr(self, '__app_public_key_cert_string__', '')
  1605. return getattr(self, '__app_public_key_cert_string__')
  1606. @property
  1607. def public_key_cert_string(self):
  1608. if not hasattr(self, '__public_key_cert_string__'):
  1609. try:
  1610. if self.publicKeyCert.startswith('-----BEGIN'):
  1611. setattr(self, '__public_key_cert_string__', self.publicKeyCert)
  1612. else:
  1613. with open(self.publicKeyCert) as fp:
  1614. setattr(self, '__public_key_cert_string__', fp.read())
  1615. except Exception as e:
  1616. logger.error('{} public_key_cert_string exception = {}'.format(repr(self), e.message))
  1617. setattr(self, '__public_key_cert_string__', '')
  1618. return getattr(self, '__public_key_cert_string__')
  1619. @property
  1620. def root_cert_string(self):
  1621. if not hasattr(self, '__root_cert_string__'):
  1622. try:
  1623. if self.rootCert.startswith('-----BEGIN'):
  1624. setattr(self, '__root_cert_string__', self.rootCert)
  1625. else:
  1626. with open(self.rootCert) as fp:
  1627. setattr(self, '__root_cert_string__', fp.read())
  1628. except Exception as e:
  1629. logger.error('{} root_cert_string exception = {}'.format(repr(self), e.message))
  1630. setattr(self, '__root_cert_string__', '')
  1631. return getattr(self, '__root_cert_string__')
  1632. @property
  1633. def pay_app_type(self):
  1634. return PayAppType.ALIPAY
  1635. @property
  1636. def __gateway_key__(self):
  1637. _ = [
  1638. self.occupantId,
  1639. self.appid,
  1640. self.occupant.role
  1641. ]
  1642. return APP_KEY_DELIMITER.join(_)
  1643. def to_dict(self, shadow = False):
  1644. return {
  1645. 'id': str(self.id),
  1646. 'appid': self.appid,
  1647. 'hasAlipayAppKeyPair': bool(self.app_private_key_string is not ''),
  1648. 'public_key_path': self.public_key_path,
  1649. 'app_private_key_path': self.app_private_key_path,
  1650. 'alipayPublicKey': encrypt_display(self.alipayPublicKey) if shadow else self.alipayPublicKey,
  1651. 'appPrivateKey': encrypt_display(self.appPrivateKey) if shadow else self.appPrivateKey,
  1652. 'alipayPublicKeyContent': encrypt_display(
  1653. self.alipayPublicKeyContent) if shadow else self.alipayPublicKeyContent,
  1654. 'appPublicKeyContent': encrypt_display(self.appPublicKeyContent) if shadow else self.appPublicKeyContent,
  1655. 'debug': self.debug,
  1656. 'appName': self.appName,
  1657. 'companyName': self.companyName
  1658. }
  1659. @classmethod
  1660. def get_null_app(cls):
  1661. app = cls(appid = '', public_key_path = '', app_private_key_path = '', shadow = False)
  1662. app.enable = False
  1663. app.valid = True
  1664. return app
  1665. @classmethod
  1666. def __from_gateway_key__(cls, occupant_id, tokens):
  1667. # type: (str, List)->cast(PayAppBase)
  1668. appid = tokens[0]
  1669. app = cls.objects(appid = appid).first() # type: AliApp
  1670. if not app:
  1671. raise UserServerException(u'系统配置错误,请联系平台客服(10005)')
  1672. app.occupantId = occupant_id
  1673. try:
  1674. app.role = tokens[1]
  1675. except:
  1676. app.role = ROLE.agent
  1677. return app
  1678. def new_gateway(self, app_platform_type):
  1679. from apps.web.core.payment.ali import AliPayGateway
  1680. return AliPayGateway(self)
  1681. def new_withdraw_gateway(self, is_ledger = True, gateway_version = None):
  1682. from apps.web.core.payment.ali import AliPayWithdrawGateway
  1683. if not self.enable:
  1684. return AliPayWithdrawGateway(self, False)
  1685. else:
  1686. return AliPayWithdrawGateway(self, is_ledger)
  1687. @property
  1688. def client(self):
  1689. if hasattr(self, '__client__'):
  1690. return getattr(self, '__client__')
  1691. if self.signKeyType not in ['normal', 'cert']:
  1692. raise AliException(
  1693. errCode = -1,
  1694. errMsg = u'参数配置错误',
  1695. client = None)
  1696. params = {
  1697. 'appid': self.appid,
  1698. 'sign_type': "RSA2",
  1699. 'debug': self.debug,
  1700. 'timeout': 15
  1701. }
  1702. params.update({'app_private_key_string': self.app_private_key_string})
  1703. if self.signKeyType == 'normal':
  1704. params.update({'public_key_string': self.public_key_string})
  1705. setattr(self, '__client__', AliPay(**params))
  1706. if self.signKeyType == 'cert':
  1707. params.update({
  1708. 'public_key_cert_string': self.public_key_cert_string,
  1709. 'root_cert_string': self.root_cert_string,
  1710. 'app_public_key_cert_string': self.app_public_key_cert_string,
  1711. 'aes_encrypt_key': self.aes_encrypt_key
  1712. })
  1713. setattr(self, '__client__', DCAliPay(**params))
  1714. return getattr(self, '__client__')
  1715. class RuralCreditUnionApp(PayAppBase):
  1716. appCode = StringField(verbose_name = u'平台号')
  1717. MerNbr = StringField(verbose_name = u'一级商户号')
  1718. EncryptSubMerchantId = StringField(verbose_name = u'加密二级商户号')
  1719. SubMerchantId = StringField(verbose_name = u'二级商户号', unique = True, required = True, null = False)
  1720. SubMerchantName = StringField(verbose_name = u'二级商户号名称')
  1721. branchId = StringField(verbose_name = u'交易机构号')
  1722. entoperId = StringField(verbose_name = u'操作员ID')
  1723. channelId = StringField(verbose_name = u'交易渠道号', default = '45')
  1724. pfx_path = StringField(verbose_name = u'私钥pfx路径', default = '')
  1725. pub_path = StringField(verbose_name = u'公钥cer路径', default = '')
  1726. pfx_passwd = StringField(verbose_name = u'证书密码', default = '')
  1727. authCodeUrl = StringField(verbose_name = u'获取autoCode的url', default = '')
  1728. SericeCenterUrl = StringField(verbose_name = u'公网UAT接入,交易上送', default = '')
  1729. privateKey = StringField(verbose_name = u'pfx私钥', default = '')
  1730. publicKey = StringField(verbose_name = u'验签公钥', default = '')
  1731. meta = {
  1732. 'indexes': [
  1733. {
  1734. 'fields': ['SubMerchantId'], 'unique': True
  1735. },
  1736. {
  1737. 'fields': ['wxSubMchId']
  1738. }
  1739. ],
  1740. 'collection': 'rural_credit_union_pay_app',
  1741. 'db_alias': 'default'
  1742. }
  1743. @property
  1744. def __valid_check__(self):
  1745. if not self.privateKey:
  1746. import OpenSSL
  1747. pfx = open(self.pfx_path, 'rb').read()
  1748. p12 = OpenSSL.crypto.load_pkcs12(pfx, self.pfx_passwd)
  1749. self.privateKey = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey())
  1750. self.save()
  1751. if not self.publicKey:
  1752. self.publicKey = open(self.pub_path, 'rb').read()
  1753. self.save()
  1754. return bool(
  1755. self.appCode and self.SubMerchantId and self.branchId and self.channelId and self.privateKey and self.publicKey)
  1756. def new_gateway(self, app_platform_type):
  1757. from apps.web.core.payment.rcu import RuralCreditUnionPaymentGateway
  1758. return RuralCreditUnionPaymentGateway(self, app_platform_type)
  1759. @property
  1760. def pay_app_type(self):
  1761. return PayAppType.RCU
  1762. @property
  1763. def __gateway_key__(self):
  1764. _ = [
  1765. self.occupantId,
  1766. self.SubMerchantId,
  1767. self.occupant.role
  1768. ]
  1769. return APP_KEY_DELIMITER.join(_)
  1770. @classmethod
  1771. def get_null_app(cls):
  1772. app = cls(merchant_no = '')
  1773. app.enable = False
  1774. app.valid = True
  1775. return app
  1776. def to_dict(self, shadow = True):
  1777. return {
  1778. 'id': str(self.id),
  1779. 'appCode': self.appCode,
  1780. 'MerNbr': self.MerNbr,
  1781. 'EncryptSubMerchantId': self.EncryptSubMerchantId,
  1782. 'SubMerchantId': self.SubMerchantId,
  1783. 'SubMerchantName': self.SubMerchantName,
  1784. 'branchId': self.branchId,
  1785. 'entoperId': self.entoperId,
  1786. 'channelId': self.channelId,
  1787. 'companyName': self.companyName,
  1788. 'appName': self.appName,
  1789. 'remarks': self.remarks,
  1790. 'type': self.pay_app_type,
  1791. }
  1792. @classmethod
  1793. def __from_gateway_key__(cls, occupant_id, tokens):
  1794. # type: (str, List)->cast(PayAppBase)
  1795. SubMerchantId = tokens[0]
  1796. app = cls.objects(SubMerchantId = SubMerchantId).first() # type: JDAggrePayApp
  1797. if not app:
  1798. raise UserServerException(u'系统配置错误,请联系平台客服')
  1799. app.occupantId = occupant_id
  1800. try:
  1801. app.role = tokens[1]
  1802. except:
  1803. app.role = ROLE.agent
  1804. return app
  1805. class PlatformAppBase(PayAppBase):
  1806. """
  1807. 平台记账APP
  1808. """
  1809. meta = {
  1810. 'abstract': True
  1811. }
  1812. APP_NAME = ''
  1813. @property
  1814. def __valid_check__(self):
  1815. return True
  1816. @property
  1817. def __gateway_key__(self):
  1818. _ = [
  1819. settings.MY_PRIMARY_AGENT_ID,
  1820. ROLE.agent
  1821. ]
  1822. return APP_KEY_DELIMITER.join(_)
  1823. @classmethod
  1824. def get_app(cls):
  1825. app = cls()
  1826. app.role = ROLE.agent
  1827. app.occupantId = settings.MY_PRIMARY_AGENT_ID
  1828. app.enable = True
  1829. app.valid = True
  1830. app.inhouse = True
  1831. app.debug = False
  1832. app.companyName = u'武汉大源科技有限公司'
  1833. app.appName = cls.APP_NAME
  1834. return app
  1835. @classmethod
  1836. def __from_gateway_key__(cls, occupant_id, tokens):
  1837. # type: (str, List)->cast(PayAppBase)
  1838. assert occupant_id == settings.MY_PRIMARY_AGENT_ID, u'非法GatewayKey'
  1839. return cls.get_app()
  1840. class PlatformPromotionApp(PlatformAppBase):
  1841. """
  1842. 用于平台记账后 分账给其他人, 例如红包受益 广告收益等 分给经销商
  1843. """
  1844. meta = {
  1845. 'abstract': True
  1846. }
  1847. APP_NAME = u'平台补贴'
  1848. def __repr__(self):
  1849. return '<platform promotion App>'
  1850. def new_gateway(self, app_platform_type):
  1851. from apps.web.core.payment.platform import PlatformPromotionPaymentGateway
  1852. return PlatformPromotionPaymentGateway(self, app_platform_type)
  1853. @property
  1854. def pay_app_type(self):
  1855. return PayAppType.PLATFORM_PROMOTION
  1856. class PlatformReconcileApp(PlatformAppBase):
  1857. """
  1858. 平台对账后建立订单分账
  1859. """
  1860. meta = {
  1861. 'abstract': True
  1862. }
  1863. APP_NAME = u'平台对账'
  1864. def __repr__(self):
  1865. return '<platform reconcile App>'
  1866. def new_gateway(self, app_platform_type):
  1867. from apps.web.core.payment.platform import PlatformReconcilePaymentGateway
  1868. return PlatformReconcilePaymentGateway(self, app_platform_type)
  1869. @property
  1870. def pay_app_type(self):
  1871. return PayAppType.PLATFORM_RECONCILE
  1872. class PlatformWalletApp(PlatformPromotionApp):
  1873. """
  1874. 用于使用经销商余额进行SIM卡支付
  1875. """
  1876. meta = {
  1877. 'abstract': True
  1878. }
  1879. def __repr__(self):
  1880. return '<platform wallet App>'
  1881. def new_gateway(self, app_platform_type):
  1882. from apps.web.core.payment.platform import PlatformWalletPaymentGateway
  1883. return PlatformWalletPaymentGateway(self, app_platform_type)
  1884. @property
  1885. def pay_app_type(self):
  1886. return PayAppType.PLATFORM_WALLET
  1887. class LedgerConsumeApp(PayAppBase):
  1888. meta = {
  1889. 'abstract': True
  1890. }
  1891. APP_NAME = u'消费分账'
  1892. @property
  1893. def pay_app_type(self):
  1894. return PayAppType.LEDGER_CONSUME
  1895. def new_gateway(self, app_platform_type):
  1896. from apps.web.core.payment.consume import LedgerConsumePaymentGateway
  1897. return LedgerConsumePaymentGateway(self, app_platform_type)
  1898. @classmethod
  1899. def get_app(cls, dealer): # type:(Dealer) -> LedgerConsumeApp
  1900. agent = dealer.productAgent
  1901. app = cls()
  1902. app.role = ROLE.agent
  1903. app.occupantId = str(agent.id)
  1904. app.enable = True
  1905. app.valid = True
  1906. app.inhouse = True
  1907. app.debug = False
  1908. app.appName = cls.APP_NAME
  1909. return app
  1910. class JDOpenPayApp(PayAppBase):
  1911. customerNum = StringField(verbose_name = u'商户编号', unique = True, required = True, null = False)
  1912. shopNum = StringField(verbose_name = u'店铺编号', required = True, null = False)
  1913. accessKey = StringField(verbose_name=u"accessKey", default=None)
  1914. secretKey = StringField(verbose_name=u"secretKey", default=None)
  1915. agent = LazyReferenceField(document_type=MerchantAgent)
  1916. meta = {
  1917. 'indexes': [
  1918. {
  1919. 'fields': ['customerNum'], 'unique': True
  1920. },
  1921. {
  1922. 'fields': ['wxSubMchId']
  1923. }
  1924. ],
  1925. 'collection': 'jdopen_pay_app',
  1926. 'db_alias': 'default'
  1927. }
  1928. def __repr__(self):
  1929. return '<JDOpenPayApp customerNum={} shopNum={}>'.format(self.customerNum, self.shopNum)
  1930. @property
  1931. def __valid_check__(self):
  1932. # return bool(self.customerNum) and bool(self.shopNum)
  1933. return bool(self.customerNum)
  1934. @property
  1935. def pay_app_type(self):
  1936. return PayAppType.JD_OPEN
  1937. @property
  1938. def __gateway_key__(self):
  1939. _ = [
  1940. self.occupantId,
  1941. self.customerNum,
  1942. self.occupant.role
  1943. ]
  1944. return APP_KEY_DELIMITER.join(_)
  1945. @classmethod
  1946. def get_null_app(cls):
  1947. app = cls(merchant_no = '')
  1948. app.enable = False
  1949. app.valid = True
  1950. return app
  1951. @classmethod
  1952. def __from_gateway_key__(cls, occupant_id, tokens):
  1953. # type: (str, List)->cast(PayAppBase)
  1954. customerNum = tokens[0]
  1955. app = cls.objects(customerNum = customerNum).first() # type: JDOpenPayApp
  1956. if not app:
  1957. raise UserServerException(u'系统配置错误,请联系平台客服')
  1958. app.occupantId = occupant_id
  1959. try:
  1960. app.role = tokens[1]
  1961. except:
  1962. app.role = ROLE.agent
  1963. return app
  1964. def new_gateway(self, app_platform_type):
  1965. from apps.web.core.payment.jdopen import JDOpenPaymentGateway
  1966. return JDOpenPaymentGateway(self, app_platform_type)
  1967. def to_dict(self, shadow = True):
  1968. return {
  1969. 'id': str(self.id),
  1970. 'customerNum': self.customerNum,
  1971. 'companyName': self.companyName,
  1972. 'appName': self.appName,
  1973. 'remarks': self.remarks
  1974. }
  1975. @classmethod
  1976. def get_or_create_by_merchant(cls, source): # type: (JDOpenApplyInfo) -> JDOpenPayApp
  1977. app = cls.objects.filter(agentNum=source.agentNum, customerNum=source.customerNum)
  1978. if not app:
  1979. app = cls(agentNum=source.agentNum, customerNum=source.customerNum)
  1980. app.settleNum = source.settleNum
  1981. app.shopNum = source.shopNum
  1982. app.accessKey, app.secretKey = source.get_access_and_secret()
  1983. return app.save()
  1984. @property
  1985. def split_id(self):
  1986. return self.customerNum
  1987. def bill_split_rule(self, partition_map):
  1988. """
  1989. 分账list必须全部包括收款方和分账方,分账金额保留两位小数,且分账金额相加需等于订单金额
  1990. :return:
  1991. """
  1992. partitions = list(flatten(partition_map.values()))
  1993. rv = []
  1994. for partition in partitions:
  1995. if RMB(partition['money']) > RMB(0):
  1996. rv.append({
  1997. 'customerNum': partition['merchantId'],
  1998. 'amount': str(RMB(partition['money']))
  1999. })
  2000. if len(rv) <= 1:
  2001. return None, None, None
  2002. else:
  2003. return 'FIXED', 'RECEIVER', rv
  2004. def refund_bill_split_list(self, partition_map):
  2005. """
  2006. 京东退款分账规则:
  2007. 1、即使退款为0, 也必须带上拉起支付的商户号
  2008. 2、所以参与支付分账的商户, 即使金额为0, 也必须带上
  2009. :return:
  2010. """
  2011. ownerPartition = partition_map.get(PARTITION_ROLE.OWNER)[0]
  2012. if 'merchantId' not in ownerPartition:
  2013. return None
  2014. partitions = list(flatten(partition_map.values()))
  2015. rv = []
  2016. for partition in partitions:
  2017. if 'merchantId' in partition:
  2018. rv.append({
  2019. 'customerNum': partition['merchantId'],
  2020. 'amount': str(abs(RMB(partition['money'])))
  2021. })
  2022. if len(rv) <= 1:
  2023. return None
  2024. else:
  2025. return rv
  2026. class ManualPayApp(PayAppBase):
  2027. """
  2028. 客户线下打款后, 超级管理员建立充值记录
  2029. """
  2030. def __repr__(self):
  2031. return '<manual pay App>'
  2032. @property
  2033. def __valid_check__(self):
  2034. return True
  2035. def new_gateway(self, app_platform_type):
  2036. from apps.web.core.payment.manual import ManualPaymentGateway
  2037. return ManualPaymentGateway(self, app_platform_type)
  2038. @property
  2039. def pay_app_type(self):
  2040. return PayAppType.MANUAL
  2041. @property
  2042. def __gateway_key__(self):
  2043. _ = [
  2044. self.occupantId,
  2045. self.occupant.role
  2046. ]
  2047. return APP_KEY_DELIMITER.join(_)
  2048. @classmethod
  2049. def get_null_app(cls):
  2050. app = cls()
  2051. app.enable = True
  2052. app.valid = True
  2053. return app
  2054. @classmethod
  2055. def __from_gateway_key__(cls, occupant_id, tokens):
  2056. # type: (str, List)->cast(PayAppBase)
  2057. app = cls.get_null_app() # type: ManualPayApp
  2058. app.occupantId = occupant_id
  2059. try:
  2060. app.role = tokens[1]
  2061. except:
  2062. app.role = ROLE.agent
  2063. return app
  2064. class SwapPayApp(PayAppBase):
  2065. """
  2066. 互联互通支付APP
  2067. """
  2068. def __repr__(self):
  2069. return '<swap pay App>'
  2070. @property
  2071. def __valid_check__(self):
  2072. return True
  2073. def new_gateway(self, app_platform_type):
  2074. from apps.web.core.payment.manual import ManualPaymentGateway
  2075. return ManualPaymentGateway(self, app_platform_type)
  2076. @property
  2077. def pay_app_type(self):
  2078. return PayAppType.SWAP
  2079. @property
  2080. def __gateway_key__(self):
  2081. _ = [
  2082. self.occupantId,
  2083. self.occupant.role
  2084. ]
  2085. return APP_KEY_DELIMITER.join(_)
  2086. @classmethod
  2087. def get_null_app(cls):
  2088. app = cls()
  2089. app.enable = True
  2090. app.valid = True
  2091. return app
  2092. @classmethod
  2093. def __from_gateway_key__(cls, occupant_id, tokens):
  2094. # type: (str, List)->cast(PayAppBase)
  2095. app = cls.get_null_app() # type: SwapPayApp
  2096. app.occupantId = occupant_id
  2097. try:
  2098. app.role = tokens[1]
  2099. except:
  2100. app.role = ROLE.agent
  2101. return app
  2102. class WechatComponentApp(DynamicDocument):
  2103. appid = StringField(verbose_name='appid', default='', required=True)
  2104. secret = StringField(verbose_name="secretId", default='', required=True)
  2105. aesKey = StringField(verbose_name="aesKey", default='', required=True)
  2106. token = StringField(verbose_name="token", default='', required=True)
  2107. companyName = StringField(verbose_name="companyName", default='')
  2108. appName = StringField(verbose_name="appName", default='')
  2109. meta = {
  2110. 'collection': 'wechat_component_app'
  2111. }
  2112. class WechatAuthorizer(DynamicDocument):
  2113. appid = StringField(verbose_name='appid', default='', required=True)
  2114. appType = IntField(verbose_name='appType(0-mini,1-biz)', default=1)
  2115. serviceType = IntField(verbose_name='serviceType', default=2)
  2116. verifyInfo = IntField(verbose_name='verifyInfo')
  2117. nickName = StringField(verbose_name='appType', default='')
  2118. userName = StringField(verbose_name='appType', default='')
  2119. headImg = StringField(verbose_name='appType', default='')
  2120. qrcodeUrl = StringField(verbose_name='appType', default='')
  2121. principalName = StringField(verbose_name='appType', default='')
  2122. funcList = ListField(verbose_name='funcList', default=[])
  2123. appStatus = IntField(verbose_name='appStatus')
  2124. extra = DictField(verbose_name='extra', default={})
  2125. refreshToken = StringField(verbose_name='appType', default='')
  2126. updateTime = DateTimeField(default=datetime.datetime.now, verbose_name=u'授权时间')
  2127. dtAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'授权时间')
  2128. authState = StringField(verbose_name='authState', default='authorized')
  2129. meta = {
  2130. 'collection': 'wechat_authorizer'
  2131. }
  2132. REFRESH_TOKEN_KEY = 'refresh_token_{}'
  2133. @classmethod
  2134. def getAuthRecord(cls, appid):
  2135. token = serviceCache.get(cls.REFRESH_TOKEN_KEY.format(appid))
  2136. if not token:
  2137. authorizer = cls.objects(appid=appid).first()
  2138. if authorizer:
  2139. token = authorizer.refreshToken
  2140. serviceCache.set(cls.REFRESH_TOKEN_KEY.format(settings.WECHAT_3RD_APPID), token)
  2141. return token
  2142. @classmethod
  2143. def createOrUpdateAuthRecord(cls, payload):
  2144. payload.update({
  2145. 'authState': 'authorized',
  2146. 'updateTime': datetime.datetime.now()
  2147. })
  2148. cls.objects(appid=payload['appid']).update_one(upsert=True, **payload)
  2149. @classmethod
  2150. def deleteAuthRecord(cls, appid):
  2151. cls.objects(appid=appid).update_one(
  2152. appid=appid, authState='unauthorized', updateTime=datetime.datetime.now())
  2153. class WithdrawEntity(EmbeddedDocument):
  2154. wechatWithdrawApp = LazyReferenceField(document_type = WechatPayApp, default = None)
  2155. alipayWithdrawApp = LazyReferenceField(document_type = AliApp, default = None)
  2156. @property
  2157. def alipay_app(self):
  2158. if self.alipayWithdrawApp:
  2159. return self.alipayWithdrawApp.fetch()
  2160. else:
  2161. return None
  2162. @property
  2163. def wechat_app(self):
  2164. if self.wechatWithdrawApp:
  2165. return self.wechatWithdrawApp.fetch()
  2166. else:
  2167. return None
  2168. class BoundOpenInfo(EmbeddedDocument):
  2169. openId = StringField(required = True, default = '')
  2170. avatar = StringField(default = '')
  2171. sex = IntField(verbose_name = "性别", default = Const.USER_SEX.UNKNOWN)
  2172. nickname = StringField(verbose_name = "昵称", default = "")
  2173. def to_dict(self):
  2174. return {
  2175. 'openId': self.openId
  2176. }
  2177. def to_detail_dict(self):
  2178. return {
  2179. "openId": self.openId,
  2180. "avatar": self.avatar,
  2181. "sex": self.sex,
  2182. "nickname": self.nickname
  2183. }
  2184. class OfflineTask(Searchable):
  2185. _STATUS_MAP = Const.CELERY_TASK_RESULT_TRANSLATION
  2186. name = StringField(verbose_name = '任务名称')
  2187. type = StringField(verbose_name = '类型')
  2188. status = StringField(verbose_name = '状态', default = 'PENDING')
  2189. result = StringField(verbose_name = '执行结果详细信息', default = '')
  2190. link = StringField(verbose_name = '链接')
  2191. celery_task_id = StringField(verbose_name = 'celery侧uuid', default = '')
  2192. process_func_name = StringField(verbose_name = '任务的函数名')
  2193. role = StringField(verbose_name = '角色', choices = ROLE.choices())
  2194. ownerId = ObjectIdField(verbose_name = '创建者ID')
  2195. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '任务生成时间')
  2196. finishedTime = DateTimeField(verbose_name = '完成时间', default = None)
  2197. search_fields = ('id', 'celery_task_id', 'name')
  2198. meta = {
  2199. 'collection': 'offline_task',
  2200. 'db_alias': 'logdata',
  2201. 'indexes': [
  2202. {
  2203. 'fields': ['ownerId', 'role']
  2204. }
  2205. ],
  2206. }
  2207. def to_dict(self):
  2208. return {
  2209. 'id': str(self.id),
  2210. 'name': self.name,
  2211. 'status': self.status,
  2212. 'result': self.result,
  2213. 'link': self.link,
  2214. 'celery_task_id': self.celery_task_id,
  2215. 'type': self.type,
  2216. 'startTime': self.dateTimeAdded,
  2217. 'finishedTime': self.finishedTime
  2218. }
  2219. @classmethod
  2220. def issue_export_report(cls, offline_task_name, process_func_name, task_type, userid, role, ext = 'xlsx'):
  2221. file_path = '{}reports/{}/{}.{}'.format(
  2222. settings.MEDIA_URL,
  2223. datetime.datetime.now().strftime("%Y%m%d"),
  2224. offline_task_name,
  2225. ext)
  2226. if settings.UPLOAD_REPORT_TO_OSS:
  2227. link = '{}{}'.format(settings.OSS_RESOURCE_URL, file_path)
  2228. else:
  2229. link = '{}{}?local=true'.format(settings.LOCAL_REPORT_URL, file_path)
  2230. offline_task = OfflineTask(
  2231. name = offline_task_name,
  2232. type = task_type,
  2233. process_func_name = process_func_name,
  2234. ownerId = userid,
  2235. role = role,
  2236. link = link)
  2237. offline_task.save()
  2238. return file_path[1:], offline_task
  2239. @classmethod
  2240. def issue(cls, offline_task_name, process_func_name, task_type, userid, role):
  2241. offline_task = OfflineTask(
  2242. name = offline_task_name,
  2243. type = task_type,
  2244. process_func_name = process_func_name,
  2245. ownerId = userid,
  2246. role = role)
  2247. offline_task.save()
  2248. return offline_task
  2249. class BankCard(Searchable):
  2250. class AccountType(object):
  2251. PERSONAL = 'personal'
  2252. PUBLIC = 'public'
  2253. bankName = StringField(verbose_name = u"银行名称", required = True)
  2254. branchName = StringField(verbose_name = u"支行信息")
  2255. cardNo = StringField(verbose_name = u"银行卡号", required = True, unique = True)
  2256. cardType = StringField(verbose_name = u"卡类型", default = "debit")
  2257. remark = StringField(verbose_name = u"备注", default = "")
  2258. # category = StringField(verbose_name=u"所属类别,个人或者公司", default='individual')
  2259. province = StringField(verbose_name = u"省级别开户地址信息", default = "")
  2260. city = StringField(verbose_name = u"市级别开户地址信息", default = "")
  2261. district = StringField(verbose_name = u"区级别开户地址信息", default = "")
  2262. holderName = StringField(verbose_name = u"开户人姓名", required = True)
  2263. code = StringField(verbose_name = u"微信支持银行卡所对应的code(银行编号), 默认为0000表示不支持", default = "0000")
  2264. cnapsCode = StringField(verbose_name = u'支行联行号', default = '')
  2265. is_valid = BooleanField(verbose_name = u"是否是有效卡", default = True)
  2266. is_primary = BooleanField(verbose_name = u"优先选择", default = True)
  2267. manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False)
  2268. accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = AccountType.PERSONAL)
  2269. meta = {"collection": "bankcards", "db_alias": "default"}
  2270. class SystemSettings(Searchable):
  2271. key = StringField(verbose_name = u'设置名称')
  2272. value = DynamicField(verbose_name = u'设置参数')
  2273. desc = StringField(verbose_name = u'设置描述')
  2274. dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加时间')
  2275. meta = {
  2276. 'collection': 'system_settings'
  2277. }
  2278. all_settings = {}
  2279. load_ready = False
  2280. @classmethod
  2281. def get_system_setting(cls, setting_name, default = None):
  2282. if not cls.load_ready:
  2283. cls.load_settings()
  2284. return cls.all_settings.get(setting_name, default)
  2285. @classmethod
  2286. def load_settings(cls):
  2287. cls.all_settings = {item.key: item.value for item in cls.objects.all()}
  2288. cls.load_ready = True
  2289. @classmethod
  2290. def reload_settings(cls):
  2291. logger.debug("reload system settings.")
  2292. cls.load_ready = False
  2293. cls.load_settings()
  2294. @classmethod
  2295. def set_setting(cls, key, value):
  2296. cls.objects(key=key).update_one(upsert=True, **{"value": value})
  2297. cls.all_settings[key] = value
  2298. @classmethod
  2299. def set_mem_setting(cls, key, value):
  2300. cls.all_settings[key] = value
  2301. @classmethod
  2302. def get_support_redpack_list(cls):
  2303. obj = cls.objects.filter(key='SUPPORT_REDPACK_LIST').first()
  2304. if obj:
  2305. return json_loads(obj.value)
  2306. else:
  2307. return []
  2308. @classmethod
  2309. def set_support_redpack_list(cls, dataList):
  2310. value = json_dumps(dataList)
  2311. return cls.objects.filter(key='SUPPORT_REDPACK_LIST').update(value=value)
  2312. @classmethod
  2313. def disable_alipay_ruhui(cls):
  2314. item = cls.objects.filter(key='DISABLE_REDPACK').first()
  2315. if not item:
  2316. vaule = {}
  2317. else:
  2318. vaule = item.value
  2319. return vaule.get('RUHUI', False)
  2320. @classmethod
  2321. def disable_alipay_laxin(cls):
  2322. item = cls.objects.filter(key='DISABLE_REDPACK').first()
  2323. if not item:
  2324. vaule = {}
  2325. else:
  2326. vaule = item.value
  2327. return vaule.get('LAXIN', False)
  2328. class DriverCode(Searchable):
  2329. code = StringField(verbose_name = u'驱动编码', unique = True)
  2330. name = StringField(verbose_name = u'驱动名称', unique = True)
  2331. description = StringField(verbose_name = u'驱动描述')
  2332. createdTime = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间')
  2333. adapterFile = StringField(verbose_name = '业务处理文件名称', default = '')
  2334. adapterVer = StringField(verbose_name = '业务版本', default = '2.0') # 发生变化后,会自动化加载
  2335. adapter = StringField(verbose_name = '适配器类名称', default = '')
  2336. eventerFile = StringField(verbose_name = '事件处理文件名称', default = '')
  2337. eventerVer = StringField(verbose_name = '事件处理版本', default = '2.0') # 发生变化后,会自动化加载
  2338. features = DictField(verbose_name = "额外特征", default = {}) # 属于某个设备类型的特征
  2339. meta = {'collection': 'device_driver_code', 'db_alias': 'default'}
  2340. def __repr__(self):
  2341. return '<DriverCode code=%s>' % (self.code,)
  2342. def to_dict(self):
  2343. return {
  2344. 'code': self.code,
  2345. 'name': self.name,
  2346. 'features': self.features,
  2347. 'description': self.description
  2348. }
  2349. @classmethod
  2350. def get_driver_code(cls, code):
  2351. # type: (str)->dict
  2352. cache_key = lambda code: 'driverCode{}'.format(code)
  2353. if not code: return None
  2354. driverInfo = cache.get(cache_key(code))
  2355. if driverInfo:
  2356. return driverInfo
  2357. else:
  2358. obj = cls.objects(code = code).first() # type: DriverCode
  2359. if obj:
  2360. value = {
  2361. 'id': str(obj.id),
  2362. 'code': obj.code,
  2363. 'name': obj.name,
  2364. 'description': obj.description,
  2365. 'createdTime': obj.to_datetime_str(obj.createdTime),
  2366. 'adapterFile': obj.adapterFile,
  2367. 'eventerFile': obj.eventerFile,
  2368. 'features': dict(obj.features)
  2369. }
  2370. cache.set(cache_key(code), value)
  2371. return value
  2372. return None
  2373. class DriverAdapter(DynamicDocument):
  2374. adapterFile = StringField(verbose_name = '业务处理文件名称', unique = True)
  2375. adapterVer = StringField(verbose_name = '业务版本', default = '1.0')
  2376. adapter = StringField(verbose_name = '适配器类名称', default = '')
  2377. meta = {'collection': 'device_driver_adapter', 'db_alias': 'default'}
  2378. _lock = ThreadLock()
  2379. adapters = {}
  2380. def to_dict(self):
  2381. return {
  2382. 'adapterFile': self.adapterFile,
  2383. 'adapterVer': self.adapterVer,
  2384. 'adapter': self.adapter
  2385. }
  2386. @classmethod
  2387. def get_driver_adapter(cls, code, dev, online = True):
  2388. cache_key = lambda filename: 'driverAdapter{}'.format(filename)
  2389. adapter_file = None
  2390. if not online:
  2391. adapter_file = 'offline'
  2392. else:
  2393. driver_code_info = DriverCode.get_driver_code(code)
  2394. if driver_code_info:
  2395. adapter_file = driver_code_info['adapterFile']
  2396. if not adapter_file:
  2397. adapter_file = 'commonPulse'
  2398. adapter_info = cache.get(cache_key(adapter_file))
  2399. if not adapter_info:
  2400. adapter = cls.objects.get(adapterFile = adapter_file)
  2401. adapter_info = {
  2402. 'adapterFile': adapter.adapterFile,
  2403. 'adapterVer': adapter.adapterVer,
  2404. 'adapter': adapter.adapter
  2405. }
  2406. cache.set(cache_key(adapter_file), {
  2407. 'adapterFile': adapter_info['adapterFile'],
  2408. 'adapterVer': adapter_info['adapterVer'],
  2409. 'adapter': adapter_info['adapter']
  2410. })
  2411. logger.debug('[cache] load driver adapter info is: {}'.format(adapter_info))
  2412. mem_info = cls.adapters.get(adapter_file, None)
  2413. if mem_info and 'module' in mem_info and \
  2414. mem_info['adapterVer'] == adapter_info['adapterVer'] and \
  2415. mem_info['adapterFile'] == adapter_info['adapterFile'] and \
  2416. mem_info['adapter'] == adapter_info['adapter']:
  2417. adapter_module = cls.adapters[adapter_file]['module']
  2418. logger.debug('adapter module {} has loaded.'.format(adapter_module))
  2419. else:
  2420. try:
  2421. cls._lock.acquire_lock()
  2422. mem_info = cls.adapters.get(adapter_file, None)
  2423. if mem_info and 'module' in mem_info and \
  2424. mem_info['adapterVer'] == adapter_info['adapterVer'] and \
  2425. mem_info['adapterFile'] == adapter_info['adapterFile'] and \
  2426. mem_info['adapter'] == adapter_info['adapter']:
  2427. adapter_module = cls.adapters[adapter_file]['module']
  2428. logger.debug('adapter module {} has loaded.'.format(adapter_module))
  2429. else:
  2430. if adapter_file in cls.adapters:
  2431. mem_info = cls.adapters[adapter_file]
  2432. logger.info('device adapter changed, need reload, oldInfo=%s, newInfo=%s' % (
  2433. mem_info, adapter_info))
  2434. module_name = 'apps.web.core.adapter.{}'.format(adapter_info['adapterFile'])
  2435. adapter_module = import_module(module_name)
  2436. logger.info('finished import new module = {}'.format(adapter_module))
  2437. adapter_module = eval('reload({})'.format(module_name))
  2438. logger.info('finished reload new module = {}'.format(adapter_module))
  2439. adapter_info.update({'module': adapter_module})
  2440. cls.adapters[adapter_file] = adapter_info
  2441. else:
  2442. logger.info('loading device adapter, adapter info = {}'.format(adapter_info))
  2443. module_name = 'apps.web.core.adapter.{}'.format(adapter_file)
  2444. adapter_module = import_module(module_name)
  2445. logger.info('finished import new module = {}'.format(adapter_module))
  2446. adapter_info.update({'module': adapter_module})
  2447. cls.adapters[adapter_file] = adapter_info
  2448. finally:
  2449. cls._lock.release_lock()
  2450. return eval('adapter_module.%s(dev)' % (adapter_info['adapter']))
  2451. class DriverEventer(DynamicDocument):
  2452. eventerFile = StringField(verbose_name = '事件处理文件名称', unique = True)
  2453. eventerVer = StringField(verbose_name = '事件处理版本', default = '1.0') # 发生变化后,会自动化加载
  2454. meta = {'collection': 'device_driver_eventer', 'db_alias': 'default'}
  2455. _lock = ThreadLock()
  2456. eventers = {}
  2457. def to_dict(self):
  2458. return {
  2459. 'id': str(self.id),
  2460. 'eventerFile': self.eventerFile,
  2461. 'eventerVer': self.eventerVer
  2462. }
  2463. @classmethod
  2464. def get_driver_eventer(cls, code, device_adapter, online = True):
  2465. cache_key = lambda filename: 'driverEventer{}'.format(filename)
  2466. eventer_file = None
  2467. if not online:
  2468. eventer_file = 'offline'
  2469. else:
  2470. driver_code_info = DriverCode.get_driver_code(code)
  2471. if driver_code_info:
  2472. eventer_file = driver_code_info['eventerFile']
  2473. if not eventer_file:
  2474. if device_adapter.__class__.__name__ == 'CommonPulseAdapter':
  2475. eventer_file = 'commonPulse'
  2476. else:
  2477. eventer_file = 'dummy'
  2478. eventer_info = cache.get(cache_key(eventer_file))
  2479. if not eventer_info:
  2480. eventer = cls.objects.get(eventerFile = eventer_file)
  2481. eventer_info = {'eventerFile': eventer.eventerFile, 'eventerVer': eventer.eventerVer}
  2482. logger.debug('load driver eventer info is: {}'.format(eventer_info))
  2483. cache.set(cache_key(eventer_file),
  2484. {
  2485. 'eventerFile': eventer_info['eventerFile'],
  2486. 'eventerVer': eventer_info['eventerVer']
  2487. })
  2488. mem_info = cls.eventers.get(eventer_file, None)
  2489. if mem_info and 'module' in mem_info and mem_info['eventerVer'] == eventer_info['eventerVer'] and \
  2490. mem_info['eventerFile'] == eventer_info['eventerFile']:
  2491. eventer_module = mem_info['module']
  2492. logger.debug('eventer module {} has loaded.'.format(eventer_module))
  2493. else:
  2494. try:
  2495. cls._lock.acquire_lock()
  2496. mem_info = cls.eventers.get(eventer_file, None)
  2497. if mem_info and 'module' in mem_info and mem_info['eventerVer'] == eventer_info['eventerVer'] and \
  2498. mem_info['eventerFile'] == eventer_info['eventerFile']:
  2499. eventer_module = mem_info['module']
  2500. logger.debug('eventer module {} has loaded.'.format(eventer_module))
  2501. else:
  2502. if mem_info:
  2503. logger.info(
  2504. 'device eventer changed, need reload, oldInfo = %s, newInfo = %s' % (
  2505. mem_info, eventer_info))
  2506. module_name = 'apps.web.eventer.{}'.format(eventer_file)
  2507. eventer_module = import_module(module_name)
  2508. logger.info('finished import new eventer module = {}'.format(eventer_module))
  2509. eventer_module = eval('reload({})'.format(module_name))
  2510. logger.info('finished reload new eventer module = {}'.format(eventer_module))
  2511. eventer_info.update({'module': eventer_module})
  2512. cls.eventers[eventer_file] = eventer_info
  2513. else:
  2514. logger.info('loading device eventer, eventer info = {}'.format(eventer_info))
  2515. module_name = 'apps.web.eventer.{}'.format(eventer_file)
  2516. eventer_module = import_module(module_name)
  2517. logger.info('finished import new eventer module = {}'.format(eventer_module))
  2518. eventer_info.update({'module': eventer_module})
  2519. cls.eventers[eventer_file] = eventer_info
  2520. finally:
  2521. cls._lock.release_lock()
  2522. return eval('eventer_module.builder(device_adapter)')
  2523. class WechatServiceProvider(Searchable):
  2524. mchid = StringField(verbose_name = u'商户号', null = False)
  2525. apikey = StringField(verbose_name = u'api秘钥', default = '')
  2526. apiclient_cert_path = StringField(verbose_name = u'api证书路径', default = '')
  2527. apiclient_key_path = StringField(verbose_name = u'api证书秘钥路径', default = '')
  2528. apiclient_serial_number = StringField(verbose_name = u'api证书序列号', default = '')
  2529. sslCert = StringField(verbose_name = u'PERM证书内容', default = '')
  2530. sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '')
  2531. # v3的
  2532. apiKeyV3 = StringField(verbose_name = u'APIv3秘钥', default = '', db_field = "apikey_v3")
  2533. sslCertV3 = StringField(verbose_name = u"平台证书 用来加密敏感信息使用")
  2534. search_fields = ('mchid')
  2535. meta = {
  2536. 'indexes': [
  2537. {
  2538. 'fields': ['mchid'], 'unique': True
  2539. },
  2540. ],
  2541. 'collection': 'wechat_service_provider',
  2542. 'db_alias': 'default'
  2543. }
  2544. @property
  2545. def ssl_cert(self):
  2546. if not hasattr(self, '__ssl_cert__'):
  2547. try:
  2548. if self.sslCert.startswith('-----BEGIN'):
  2549. setattr(self, '__ssl_cert__', self.sslCert)
  2550. elif self.apiclient_cert_path.startswith('-----BEGIN'):
  2551. setattr(self, '__ssl_cert__', self.apiclient_cert_path)
  2552. else:
  2553. with open(self.apiclient_cert_path) as fp:
  2554. setattr(self, '__ssl_cert__', fp.read())
  2555. except Exception as e:
  2556. logger.error('{} ssl_cert exception = {}'.format(repr(self), e.message))
  2557. setattr(self, '__ssl_cert__', '')
  2558. return getattr(self, '__ssl_cert__')
  2559. @property
  2560. def ssl_key(self):
  2561. if not hasattr(self, '__ssl_key__'):
  2562. try:
  2563. if self.sslKey.startswith('-----BEGIN'):
  2564. setattr(self, '__ssl_key__', self.sslKey)
  2565. elif self.apiclient_key_path.startswith('-----BEGIN'):
  2566. setattr(self, '__ssl_key__', self.apiclient_key_path)
  2567. else:
  2568. with open(self.apiclient_key_path) as fp:
  2569. setattr(self, '__ssl_key__', fp.read())
  2570. except Exception as e:
  2571. logger.error('{} ssl_key exception = {}'.format(repr(self), e.message))
  2572. setattr(self, '__ssl_key__', '')
  2573. return getattr(self, '__ssl_key__')
  2574. class CustomerPayRelation(Searchable):
  2575. """
  2576. app 和使用者的关联表
  2577. 为 多对多的关系
  2578. """
  2579. ownerId = StringField(verbose_name=u"持有者ID")
  2580. ownerRole = StringField(verbose_name=u"持有者角色")
  2581. appId = StringField(verbose_name=u"app的ID")
  2582. appType = StringField(verbose_name=u"支付APP的种类")
  2583. active = BooleanField(verbose_name=u"是否处于使用状态", default=False)
  2584. isDelete = BooleanField(verbose_name=u"是否已经删除", default=False)
  2585. @classmethod
  2586. def add_relation(cls, customer, app):
  2587. rel = cls.objects.filter(ownerId=str(customer.id), appId=str(app.id))
  2588. if not rel:
  2589. rel = cls()
  2590. rel.ownerId = str(customer.id)
  2591. rel.ownerRole = str(customer.role)
  2592. rel.appId = str(app.id)
  2593. rel.appType = str(app.__class__.__name__)
  2594. return rel.save()
  2595. else:
  2596. return rel
  2597. def active_app(self):
  2598. """
  2599. 激活app 使之处于可用状态
  2600. """
  2601. self.active = True
  2602. return self.save()
  2603. def deactive_app(self):
  2604. """
  2605. 关闭app 使之处于不可用状态
  2606. """
  2607. self.active = False
  2608. return self.save()
  2609. def delete_relation(self):
  2610. """
  2611. 删除app
  2612. """
  2613. app = self.deactive_app()
  2614. app.isDelete = True
  2615. return app.save()
  2616. MERCHANT_APP_LIST = [JDAggrePayApp, SaobeiPayApp, YsPayApp, RuralCreditUnionApp, JDOpenPayApp, DlbPayApp]