123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import base64
- import datetime
- import logging
- import os
- from importlib import import_module
- import simplejson as json
- from django.conf import settings
- from django.core.cache import cache
- from django.utils.module_loading import import_string
- from mongoengine import StringField, DateTimeField, EmbeddedDocument, DictField, BooleanField, DynamicField, ListField, \
- IntField, LazyReferenceField, ObjectIdField, DynamicDocument, EmbeddedDocumentField
- from typing import List, cast, TYPE_CHECKING
- from apilib.monetary import RMB
- from apilib.utils import flatten
- from apilib.utils_json import json_loads, json_dumps
- from apilib.utils_string import encrypt_display
- from apilib.utils_sys import ThreadLock
- from apps import serviceCache
- from apps.web.constant import Const, PARTITION_ROLE
- from apps.web.core import PayAppType, APP_KEY_DELIMITER, ROLE
- from apps.web.core.db import Searchable
- from apps.web.exceptions import UserServerException
- from library.alipay import AliException, AliPay, DCAliPay
- from library.wechatpayv3 import update_certificates
- logger = logging.getLogger(__name__)
- if TYPE_CHECKING:
- from apps.web.dealer.models import Dealer
- class FAQ(Searchable):
- """
- 配置FAQ,首先让经销商|终端用户看到常用问题,找不到问题再联系客服
- """
- question = StringField(verbose_name = '问题')
- answer = StringField(verbose_name = '答案')
- target = StringField(verbose_name = '目标 := (Dealer|Agent|EndUser)', default = '*')
- devTypeId = StringField(verbose_name = '设备类型ID,默认为普适性问题,为空', default = '')
- images = ListField(verbose_name = '辅助说明图像')
- videos = ListField(verbose_name = '辅助说明视频')
- upvotes = IntField(verbose_name = '该FAQ有帮助')
- downvotes = IntField(verbose_name = '该FAQ没有帮助')
- meta = {'collection': 'faqs', 'db_alias': 'default'}
- def to_dict(self):
- return {
- 'id': str(self.id),
- 'question': self.question,
- 'answer': self.answer,
- 'target': self.target,
- 'images': self.images,
- 'videos': self.videos,
- 'devTypeId': self.devTypeId,
- 'upvotes': self.upvotes,
- 'downvotes': self.downvotes
- }
- @classmethod
- def get_by_target(cls, target):
- return cls.objects(target__in = ['*', target])
- class EmbeddedApp(EmbeddedDocument):
- meta = {
- 'abstract': True
- }
- appid = StringField(verbose_name = 'appid', default = '')
- secret = StringField(verbose_name = "secretId", default = '', max_length = 32)
- name = StringField(verbose_name = "name", default = '')
- companyName = StringField(verbose_name = "companyName", default = '')
- @property
- def debug(self):
- return False
- @property
- def occupantId(self):
- return getattr(self, '__occupant_id__', '')
- @occupantId.setter
- def occupantId(self, occupant_id):
- self.__occupant_id__ = occupant_id
- @property
- def occupant(self):
- return getattr(self, '__occupant__', None)
- @occupant.setter
- def occupant(self, occupant):
- self.__occupant__ = occupant
- @property
- def __valid_check__(self):
- return bool(self.appid and self.secret)
- @property
- def valid(self):
- if hasattr(self, '__valid__'):
- return getattr(self, '__valid__')
- else:
- if not hasattr(self, '__valid_check__'):
- raise AttributeError('no __valid_check__ attribute')
- else:
- return getattr(self, '__valid_check__')
- @valid.setter
- def valid(self, value):
- self.__valid__ = value
- @property
- def enable(self):
- return getattr(self, '__enable__', True)
- @enable.setter
- def enable(self, is_enable):
- self.__enable__ = is_enable
- @classmethod
- def get_null_app(cls):
- app = cls(appid = '', secret = '')
- app.enable = False
- app.valid = True
- return app
- @staticmethod
- def from_json_string(app_string):
- app_dict = json.loads(app_string)
- _model = app_dict.pop('model')
- _cls = import_string(_model)
- return _cls(**app_dict)
- class WechatAuthApp(EmbeddedApp):
- def __repr__(self): return '<WechatAuthApp appid=%s>' % (self.appid,)
- def to_dict(self):
- return {
- 'appid': self.appid,
- 'secret': self.secret,
- 'name': self.name,
- 'companyName': self.companyName
- }
- @property
- def client(self):
- if hasattr(self, '__client__'):
- return getattr(self, '__client__')
- class WechatManagerApp(EmbeddedApp):
- templateIdMap = DictField(verbose_name = u"微信推送消息模版ID字典", default = {})
- MESSAGE_TEMPLATE = {
- 'feedback': {
- 'templateId': '',
- 'context': json.dumps({
- "first": {
- "value": u'${title}',
- "color": "#173177"
- },
- "keyword1": {
- "value": u'${nickname}',
- "color": "#173177"
- },
- "keyword2": {
- "value": '${feedbackTime}',
- "color": "#173177"
- },
- "remark": {
- "value": "客户就是上帝,请及时处理!",
- "color": "#173177"
- }
- })
- },
- 'daily_income': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- "value": '${reportTime}',
- "color": '#173177'
- },
- 'keyword2': {
- 'value': u'${report}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'祝您今日工作愉快!',
- 'color': '#173177'
- }
- })
- },
- 'new_payment_order': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': u'${customer}',
- 'color': '#173177'
- },
- 'keyword2': {
- 'value': u'${income}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'祝您生意兴隆!',
- 'color': '#173177'
- }
- })
- },
- 'abnormal_device_offline': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- "value": u'${device}',
- "color": '#173177'
- },
- 'keyword2': {
- 'value': '${notifyTime}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'感谢您的使用,小客服随时为您服务',
- 'color': '#173177'
- }
- })
- },
- 'device_fault': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- "value": u'${device}',
- "color": '#173177'
- },
- 'keyword2': {
- 'value': u'${location}',
- 'color': '#173177'
- },
- 'keyword3': {
- 'value': u'${fault}',
- 'color': '#173177'
- },
- 'keyword4': {
- 'value': '${notifyTime}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'感谢您的使用,小客服随时为您服务',
- 'color': '#173177'
- }
- })
- },
- 'sim_expire_notify': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- "value": u'${num}',
- "color": '#173177'
- },
- 'keyword2': {
- 'value': u'${type}',
- 'color': '#173177'
- },
- 'keyword3': {
- 'value': u'${time}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'感谢您一直以来对我们工作的支持与帮助',
- 'color': '#173177'
- }
- })
- },
- 'system_alarm_notify': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- "value": u'${account}',
- "color": '#173177'
- },
- 'keyword2': {
- 'value': u'${object}',
- 'color': '#173177'
- },
- 'keyword3': {
- 'value': u'${content}',
- 'color': '#173177'
- },
- 'keyword4': {
- 'value': u'${time}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'客户就是上帝,请尽快处理',
- 'color': '#173177'
- }
- })
- },
- # 'online_notify': {
- # 'templateId': '',
- # 'context': json.dumps({
- # 'first': {
- # 'value': u'设备正常上线',
- # 'color': '#173177'
- # },
- # 'keyword1': {
- # "value": u'正常上线',
- # "color": '#173177'
- # },
- # 'keyword2': {
- # 'value': u'${device}',
- # 'color': '#173177'
- # },
- # 'keyword3': {
- # 'value': '${devNo}',
- # 'color': '#173177'
- # },
- # 'keyword4': {
- # 'value': '${devType}',
- # 'color': '#173177'
- # },
- # 'keyword5': {
- # 'value': '${address}',
- # 'color': '#173177'
- # },
- # 'remark': {
- # 'value': u'感谢您的使用,小客服随时为您服务',
- # 'color': '#173177'
- # }
- # })
- # }
- }
- def __repr__(self):
- return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
- def to_dict(self):
- template_id_map = []
- for templateName, template in self.templateIdMap.iteritems():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': template['templateId']
- })
- for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
- if templateName not in self.templateIdMap.keys():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': ''
- })
- return {
- 'appid': self.appid,
- 'secret': self.secret,
- 'name': self.name,
- 'companyName': self.companyName,
- 'templateIdMap': template_id_map
- }
- @classmethod
- def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
- __template_id_map = {}
- if templateIdMap:
- for item in templateIdMap:
- if item['key'] not in cls.MESSAGE_TEMPLATE:
- continue
- __template_id_map[item['key']] = {
- 'templateId': item['value'].strip() if item['value'] else '',
- 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
- }
- else:
- __template_id_map.update(cls.MESSAGE_TEMPLATE)
- return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
- companyName = companyName)
- class WechatUserManagerApp(EmbeddedApp):
- templateIdMap = DictField(verbose_name = u"微信推送消息模版ID字典", default = {})
- MESSAGE_TEMPLATE = {
- 'feedback_process': {
- 'templateId': '',
- 'context': json.dumps({
- "first": {
- "value": u"${title}",
- "color": "#173177"
- },
- "event": {
- "value": u"${event}",
- "color": "#173177"
- },
- "finish_time": {
- "value": '${finishTime}',
- "color": "#173177"
- },
- "remark": {
- "value": u"${remark}",
- "color": "#173177"
- }
- })
- },
- 'service_start': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': u'${service}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": u'${time}',
- "color": '#173177'
- },
- 'remark': {
- 'value': u'${remark}',
- 'color': '#173177'
- }
- })
- },
- 'service_complete': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': u'${service}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": u'${finishTime}',
- "color": '#173177'
- },
- 'remark': {
- 'value': u'${remark}',
- 'color': '#173177'
- }
- })
- },
- 'charge_order_complete': {
- 'templateId': '',
- 'context': json.dumps({
- 'keyword1': {
- 'value': u'${orderNo}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": u'${terminal}',
- "color": '#173177'
- },
- 'keyword3': {
- 'value': u'${reason}',
- 'color': '#173177'
- },
- 'keyword4': {
- 'value': u'${service}',
- 'color': '#173177'
- },
- 'keyword5': {
- 'value': u'${settle}',
- 'color': '#173177'
- }
- })
- },
- 'common_order_complete': {
- 'templateId': '',
- 'context': json.dumps({
- 'keyword1': {
- 'value': u'${orderNo}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": u'${settle}',
- "color": '#173177'
- },
- 'keyword3': {
- 'value': u'${terminal}',
- 'color': '#173177'
- },
- 'keyword4': {
- 'value': u'${startTime}',
- 'color': '#173177'
- },
- 'keyword5': {
- 'value': u'${finishTime}',
- 'color': '#173177'
- }
- })
- },
- 'refund_coins': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': u'${backCount}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": '${finishTime}',
- "color": '#173177'
- },
- 'remark': {
- 'value': u'感谢您的支持!',
- 'color': '#173177'
- }
- })
- },
- 'device_fault': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'performance': {
- "value": u'${fault}',
- "color": '#173177'
- },
- 'time': {
- 'value': u'${notifyTime}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'感谢您的使用,小客服随时为您服务',
- 'color': '#173177'
- }
- })
- },
- 'less_balance': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': '${account}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": '${balance}',
- "color": '#173177'
- },
- 'remark': {
- 'value': u'为了不影响您的使用,建议您赶快充值!',
- 'color': '#173177'
- }
- })
- },
- 'consume_notify': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': u'${serviceType}',
- 'color': '#173177'
- },
- 'keyword2': {
- 'value': u'${money}',
- 'color': '#173177'
- },
- 'keyword3': {
- "value": '${finishTime}',
- "color": '#173177'
- },
- 'remark': {
- 'value': u'如果确认不是合法刷卡使用,可以通过解绑卡,让卡不可用。',
- 'color': '#173177'
- }
- })
- },
- 'service_expired': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- 'value': u'${department}',
- 'color': '#173177'
- },
- 'keyword2': {
- "value": '${expiredTime}',
- "color": '#173177'
- },
- 'remark': {
- 'value': u'${remark}', # u'您可以打开公众号的个人中心,选择优惠卡卷,然后选择您即将过期的卡进行续费!',
- 'color': '#173177'
- }
- })
- },
- 'dev_start': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#173177'
- },
- 'keyword1': {
- "value": u'${things}',
- "color": '#173177'
- },
- 'keyword2': {
- 'value': u'${time}',
- 'color': '#173177'
- },
- 'remark': {
- 'value': u'${remark}',
- 'color': '#173177'
- }
- })
- },
- 'insurance_sub': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#000000'
- },
- 'keyword1': {
- "value": u'${name}',
- "color": '#858585'
- },
- 'keyword2': {
- 'value': u'${category}',
- 'color': '#858585'
- },
- 'keyword3': {
- 'value': u'${orderNo}',
- 'color': '#858585'
- },
- 'keyword4': {
- 'value': u'${money}',
- 'color': '#858585'
- },
- 'keyword5': {
- 'value': u'${validTime}',
- 'color': '#858585'
- },
- 'remark': {
- 'value': u'${remark}',
- 'color': '#173177'
- }
- })
- },
- 'insurance_cancel': {
- 'templateId': '',
- 'context': json.dumps({
- 'first': {
- 'value': u'${title}',
- 'color': '#858585'
- },
- 'keyword1': {
- "value": u'${orderNo}',
- "color": '#858585'
- },
- 'keyword2': {
- 'value': u'${name}',
- 'color': '#858585'
- },
- 'keyword3': {
- 'value': u'${user}',
- 'color': '#858585'
- },
- 'keyword4': {
- 'value': u'${cancelTime}',
- 'color': '#858585'
- },
- 'keyword5': {
- 'value': u'${money}',
- 'color': '#858585'
- },
- 'remark': {
- 'value': u'${remark}',
- 'color': '#173177'
- }
- })
- },
- }
- def __repr__(self):
- return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
- def to_dict(self):
- template_id_map = []
- for templateName, template in self.templateIdMap.iteritems():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': template['templateId']
- })
- for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
- if templateName not in self.templateIdMap.keys():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': ''
- })
- return {
- 'appid': self.appid,
- 'secret': self.secret,
- 'name': self.name,
- 'companyName': self.companyName,
- 'templateIdMap': template_id_map
- }
- @classmethod
- def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
- __template_id_map = {}
- if templateIdMap:
- for item in templateIdMap:
- if item['key'] not in cls.MESSAGE_TEMPLATE:
- continue
- __template_id_map[item['key']] = {
- 'templateId': item['value'].strip() if item['value'] else '',
- 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
- }
- else:
- __template_id_map.update(cls.MESSAGE_TEMPLATE)
- return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
- companyName = companyName)
- class WechatUserSubscribeManagerApp(EmbeddedApp):
- templateIdMap = DictField(verbose_name = u"微信推送消息模版ID字典", default = {})
- MESSAGE_TEMPLATE = {
- 'service_start': {
- 'templateId': '',
- 'context': json.dumps({
- 'data': {
- 'thing2': {'value': u'${service}'}, # 服务项目
- 'time4': {'value': u'${time}'}, # 开始时间
- 'thing5': {'value': u'${remark}'}, # 备注
- },
- })
- },
- 'service_complete': {
- 'templateId': '',
- 'context': json.dumps({
- 'data': {
- 'thing5': {'value': u'${service}'}, # 服务名称
- 'thing6': {'value': u'${remark}'}, # 温馨提示
- 'time3': {'value': u'${finishTime}'}, # 完成时间
- },
- })
- }
- }
- def __repr__(self):
- return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
- def to_dict(self):
- template_id_map = []
- for templateName, template in self.templateIdMap.iteritems():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': template['templateId']
- })
- for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
- if templateName not in self.templateIdMap.keys():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': ''
- })
- return {
- 'appid': self.appid,
- 'secret': self.secret,
- 'name': self.name,
- 'companyName': self.companyName,
- 'templateIdMap': template_id_map
- }
- @classmethod
- def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
- __template_id_map = {}
- if templateIdMap:
- for item in templateIdMap:
- if item['key'] not in cls.MESSAGE_TEMPLATE:
- continue
- __template_id_map[item['key']] = {
- 'templateId': item['value'].strip() if item['value'] else '',
- 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
- }
- else:
- __template_id_map.update(cls.MESSAGE_TEMPLATE)
- return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
- companyName = companyName)
- class WechatDealerSubscribeManagerApp(EmbeddedApp):
- templateIdMap = DictField(verbose_name = u"微信推送消息模版ID字典", default = {})
- MESSAGE_TEMPLATE = {
- 'service_start': {
- 'templateId': '',
- 'context': json.dumps({
- 'data': {
- 'character_string1': {'value': u'${orderNo}'}, # 订单号
- 'thing2': {'value': u'${majorDeviceType}'}, # 服务项目
- 'time4': {'value': u'${time}'}, # 开始时间
- 'thing5': {'value': u'${remarks}'}, # 备注
- },
- })
- },
- }
- def __repr__(self):
- return '<{class_name} appid={appid}>'.format(class_name = self.__class__.__name__, appid = self.appid)
- def to_dict(self):
- template_id_map = []
- for templateName, template in self.templateIdMap.iteritems():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': template['templateId']
- })
- for templateName, message_tmpl in self.MESSAGE_TEMPLATE.iteritems():
- if templateName not in self.templateIdMap.keys():
- template_id_map.append({
- 'key': templateName,
- 'name': Const.TRANSLATION[templateName] if templateName in Const.TRANSLATION else templateName,
- 'value': ''
- })
- return {
- 'appid': self.appid,
- 'secret': self.secret,
- 'name': self.name,
- 'companyName': self.companyName,
- 'templateIdMap': template_id_map
- }
- @classmethod
- def create(cls, appid, secret, templateIdMap = None, name = '', companyName = ''):
- __template_id_map = {}
- if templateIdMap:
- for item in templateIdMap:
- if item['key'] not in cls.MESSAGE_TEMPLATE:
- continue
- __template_id_map[item['key']] = {
- 'templateId': item['value'].strip() if item['value'] else '',
- 'context': cls.MESSAGE_TEMPLATE[item['key']]['context']
- }
- else:
- __template_id_map.update(cls.MESSAGE_TEMPLATE)
- return cls(appid = appid, secret = secret, templateIdMap = __template_id_map, name = name,
- companyName = companyName)
- def get_sub_template_id_list(self):
- if not self.templateIdMap:
- return []
- else:
- try:
- return map(lambda _: _['templateId'], self.templateIdMap.values())
- except Exception as e:
- logger.info('error e=<{}>'.format(e))
- return []
- class WechatChannelApp(DynamicDocument):
- mchid = StringField(verbose_name = u'mchid', null = False)
- sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '')
- apikey_v3 = StringField(verbose_name = u'v3 api key. 加解密V3接口敏感信息', default = None)
- app_serial_number = StringField(verbose_name = u'商户证书序列号', default = None)
- platform_certificates = ListField(verbose_name = u'平台证书列表', default = None)
- appid = StringField(verbose_name = u'通道应用appid', default = None)
- search_fields = ('mchid', 'name', 'remarks')
- meta = {
- 'indexes': [
- {
- 'fields': ['mchid'], 'unique': True
- },
- ],
- 'collection': 'wechat_channel_app',
- 'db_alias': 'default'
- }
- def __repr__(self):
- return '<WechatChannelApp mchid={}>'.format(self.mchid)
- @property
- def ssl_key(self):
- return self.sslKey
- class PayAppBase(Searchable):
- meta = {
- 'abstract': True
- }
- class Function(object):
- PAY = 'pay'
- WITHDRAW = 'withdraw'
- companyName = StringField(verbose_name = u'公司名称', required = True, default = '')
- appName = StringField(verbose_name = u'应用名称', required = True, default = u'自助服务')
- remarks = StringField(verbose_name = u'备注', default = '')
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加时间')
- dateTimeUpdated = DateTimeField(verbose_name = '修改时间')
- inhouse = BooleanField(verbose_name = u'是否平台商户', default = False)
- debug = BooleanField(verbose_name = u'是否测试账号', default = False)
- agentNo = StringField(verbose_name = u'商户代理商编号', default = None)
- supportWithdraw = BooleanField(verbose_name = u'是否支持提现零钱', default = False)
- supportWithdrawBank = BooleanField(verbose_name = u'是否支持提现到银行卡', default = False)
- @property
- def pay_app_type(self):
- raise AttributeError('no pay_app_type attribute')
- @property
- def occupantId(self):
- return getattr(self, '__occupant_id__', '')
- @occupantId.setter
- def occupantId(self, occupant_id):
- self.__occupant_id__ = occupant_id
- @property
- def occupant(self):
- _obj = getattr(self, '__occupant__', None)
- if not _obj:
- _obj = ROLE.from_role_id(self.role, self.occupantId)
- self.__occupant__ = _obj
- return _obj
- @occupant.setter
- def occupant(self, occupant):
- self.__occupant__ = occupant
- self.__occupant_id__ = str(occupant.id)
- self.role = occupant.role
- @property
- def role(self):
- return self.__role__
- @role.setter
- def role(self, role):
- self.__role__ = role
- @property
- def valid(self):
- if hasattr(self, '__valid__'):
- return getattr(self, '__valid__')
- else:
- if not hasattr(self, '__valid_check__'):
- raise AttributeError('no __valid_check__ attribute')
- else:
- return getattr(self, '__valid_check__')
- @valid.setter
- def valid(self, value):
- self.__valid__ = value
- @property
- def enable(self):
- # type: ()->bool
- return getattr(self, '__enable__', True)
- @enable.setter
- def enable(self, value):
- # type: (bool)->None
- self.__enable__ = value
- @property
- def __gateway_key__(self):
- raise AttributeError('no __gateway_key__ attribute')
- @property
- def __source_key__(self):
- raise AttributeError('no __source_key__ attribute')
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- def to_dict(self):
- raise NotImplementedError('must implement to_dict method')
- @classmethod
- def list(cls, page_index, page_size, search_key):
- cursor = cls.objects().search(search_key).order_by('-dateTimeAdded')
- total = cursor.count()
- items = cursor.paginate(page_index, page_size)
- return total, [_.to_dict(True) for _ in items]
- @classmethod
- def __from_gateway_key__(cls, occupant_id, tokens):
- # type: (str, List)->cast(PayAppBase)
- raise NotImplementedError('must implement from_gateway_key class method')
- def new_gateway(self, app_platform_type):
- raise NotImplementedError('must implement new_gateway')
- def new_withdraw_gateway(self, gateway_version = None):
- raise NotImplementedError('must implement new_withdraw_gateway')
- class WechatPayApp(PayAppBase):
- """
- 对于V1接口, 需要sslKey(证书私钥)和sslCert(证书字符串)来调用接口, 使用apikey来加解密信息
- 对于V3接口, 需要sslKey(证书私钥)和app_serial_number(证书序列号)来调用接口, 使用apikey_v3来加解密信息
- """
- appid = StringField(verbose_name = u'应用ID', required = True, null = False)
- secret = StringField(verbose_name = u"secretId", required = True, null = False, max_length = 32)
- mchid = StringField(verbose_name = u'mchid', null = False)
- sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '')
- # 对于V1接口, 需要sslKey(证书私钥)和sslCert(证书字符串)来调用接口, 使用apikey来加解密信息
- apikey = StringField(verbose_name = u'apikey', null = False)
- sslCert = StringField(verbose_name = u'PERM证书内容', default = '')
- # 对于V3接口, 需要sslKey(证书私钥)和app_serial_number(证书序列号)来调用接口, 使用apikey_v3来加解密信息
- apikey_v3 = StringField(verbose_name = u'v3 api key. 加解密V3接口敏感信息', default = None)
- app_serial_number = StringField(verbose_name = u'商户证书序列号', default = None)
- platform_certificates = ListField(verbose_name = u'平台证书列表', default = None)
- withdrawV3 = BooleanField(verbose_name = u'是否使用V3接口对微信转账', default = False)
- manual_withdraw = BooleanField(verbose_name = u'是否手动提现', default = False)
- # 以下字段不在使用, 暂时兼容保留
- sslcert_path = StringField(verbose_name = '证书pem格式', default = None)
- sslkey_path = StringField(verbose_name = '证书密钥pem格式', default = None)
- rawAppId = StringField(verbose_name = u'原始ID', default = '')
- rootca_path = StringField(verbose_name = u'root证书路径', default = '')
- rootCA = StringField(verbose_name = u'root证书', default = '')
- supportWithdraw = BooleanField(verbose_name = u'是否支持提现', default = True)
- supportWithdrawBank = BooleanField(verbose_name = u'是否支持提现到银行卡', default = True)
- search_fields = ('appid', 'mchid', 'name', 'remarks')
- meta = {
- 'indexes': [
- {
- 'fields': ['appid', 'mchid'], 'unique': True
- },
- ],
- 'collection': 'wechat_pay_app',
- 'db_alias': 'default'
- }
- def __repr__(self):
- return '<WechatPayApp appid={}>'.format(self.appid)
- @property
- def __valid_check__(self):
- return bool(self.appid and self.secret and self.mchid and self.ssl_key and self.ssl_cert and self.apikey)
- @property
- def pay_app_type(self):
- return PayAppType.WECHAT
- @property
- def __gateway_key__(self):
- _ = [
- self.occupantId,
- self.appid,
- self.mchid,
- self.occupant.role
- ]
- return APP_KEY_DELIMITER.join(_)
- @property
- def __source_key__(self):
- return APP_KEY_DELIMITER.join([
- self.occupantId,
- self.mchid
- ])
- def to_dict(self, shadow = False):
- return {
- 'id': str(self.id),
- 'appid': self.appid,
- 'mchid': self.mchid,
- 'secret': encrypt_display(self.secret) if shadow else self.secret,
- 'apikey': encrypt_display(self.apikey) if shadow else self.apikey,
- 'ssl_key': base64.b64encode(self.ssl_key),
- 'ssl_cert': base64.b64encode(self.ssl_cert),
- 'manual_withdraw': self.manual_withdraw,
- 'companyName': self.companyName,
- 'appName': self.appName,
- 'remarks': self.remarks
- }
- @classmethod
- def get_null_app(cls):
- app = cls(appid = '',
- secret = '',
- mchid = '',
- apikey = '',
- sslKey = '',
- sslCert = '',
- sslcert_path = '',
- sslkey_path = '',
- manual_withdraw = False)
- app.enable = False
- app.valid = True
- app.supportWithdraw = False
- app.supportWithdrawBank = False
- return app
- @classmethod
- def __from_gateway_key__(cls, occupant_id, tokens):
- # type: (str, List)->cast(PayAppBase)
- appid, mchid = tokens[0], tokens[1]
- app = cls.objects(appid = appid, mchid = mchid).first() # type: WechatPayApp
- if not app:
- raise UserServerException(u'系统配置错误,请联系平台客服')
- app.occupantId = occupant_id
- try:
- app.role = tokens[2]
- except:
- app.role = ROLE.agent
- return app
- @classmethod
- def __from_source_key__(cls, occupant_id, tokens):
- # type: (str, List)->cast(PayAppBase)
- # TODO 查找最新加的商户号. 需要增加商户号管理功能,可以禁止,设置active等功能
- app = cls.objects(mchid = tokens[0]).order_by('-dateTimeAdded').first()
- if not app:
- raise UserServerException(u'系统配置错误(第三方支付),请联系平台客服')
- app.occupantId = occupant_id
- return app
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.wechat import WechatPaymentGateway
- return WechatPaymentGateway(self)
- def new_withdraw_gateway(self, gateway_version = 'v1'):
- from apps.web.core.payment.wechat import WechatWithdrawGateway
- if gateway_version == 'v3':
- if not self.withdrawV3 or not self.apikey_v3:
- return None
- return WechatWithdrawGateway(self, gateway_version = gateway_version)
- @property
- def ssl_cert(self):
- if not hasattr(self, '__ssl_cert__'):
- try:
- if self.sslCert.startswith('-----BEGIN'):
- setattr(self, '__ssl_cert__', self.sslCert)
- elif self.sslcert_path.startswith('-----BEGIN'):
- setattr(self, '__ssl_cert__', self.sslcert_path)
- else:
- with open(self.sslcert_path) as fp:
- setattr(self, '__ssl_cert__', fp.read())
- except Exception as e:
- logger.error('{} ssl_cert exception = {}'.format(repr(self), e.message))
- setattr(self, '__ssl_cert__', '')
- return getattr(self, '__ssl_cert__')
- @property
- def ssl_key(self):
- if not hasattr(self, '__ssl_key__'):
- try:
- if self.sslKey.startswith('-----BEGIN'):
- setattr(self, '__ssl_key__', self.sslKey)
- elif self.sslkey_path.startswith('-----BEGIN'):
- setattr(self, '__ssl_key__', self.sslkey_path)
- else:
- with open(self.sslkey_path) as fp:
- setattr(self, '__ssl_key__', fp.read())
- except Exception as e:
- logger.error('{} ssl_key exception = {}'.format(repr(self), e.message))
- setattr(self, '__ssl_key__', '')
- return getattr(self, '__ssl_key__')
- @update_certificates.connect
- def update_v3_certificates(sender, mchid, cert_str_list):
- WechatChannelApp.objects(mchid = mchid).update(platform_certificates = cert_str_list)
- WechatPayApp.objects(mchid = mchid).update(platform_certificates = cert_str_list)
- class AliApp(PayAppBase):
- appid = StringField(verbose_name = u'应用ID', required = True, null = False)
- # 参数加密秘钥. 一般不加密
- aesEncryptKey = StringField(verbose_name = u'加密秘钥', default = None)
- signKeyType = StringField(verbose_name = u'签名方式(normal,cert)', default = 'normal')
- # 公钥模式
- appPrivateKey = StringField(verbose_name = u'支付宝应用私钥. 只有测试情况下才能使用字符串', default = '')
- alipayPublicKey = StringField(verbose_name = u'文本形式支付宝账户公钥', default = '')
- # 证书模式
- appPublicKeyCert = StringField(verbose_name = u'支付宝应用公共证书', default = '')
- publicKeyCert = StringField(verbose_name = u'支付宝公共证书', default = '')
- rootCert = StringField(verbose_name = u'支付宝根证书', default = '')
- # 中间字段
- alipayPublicKeyContent = StringField(verbose_name = u'支付宝公钥文本', default = None)
- appPublicKeyContent = StringField(verbose_name = u'应用公钥文本', default = None)
- app_private_key_path = StringField(verbose_name = u'支付宝应用私钥路径', default = None)
- public_key_path = StringField(verbose_name = u'支付宝账户公钥路径', default = None)
- app_publickey_cert_path = StringField(verbose_name = u'支付宝应用公共证书路径', default = None)
- publickey_cert_path = StringField(verbose_name = u'支付宝公共证书路径', default = None)
- root_cert_path = StringField(verbose_name = u'支付宝根证书路径', default = None)
- search_fields = ('appid', 'name', 'remarks')
- meta = {
- 'indexes': [
- {
- 'fields': ['appid'], 'unique': True
- },
- ],
- 'collection': 'ali_pay_app',
- 'db_alias': 'default'
- }
- def __repr__(self):
- return '<AliApp appid={}>'.format(self.appid)
- @property
- def __valid_check__(self):
- if not self.appid:
- return False
- if self.signKeyType == 'normal':
- return bool(self.public_key_string and self.app_private_key_string)
- if self.signKeyType == 'cert':
- return bool(self.app_private_key_string and
- self.public_key_cert_string and
- self.root_cert_string and
- self.app_public_key_cert_string)
- return False
- @property
- def aes_encrypt_key(self):
- return self.aesEncryptKey
- @property
- def app_private_key_string(self):
- if not hasattr(self, '__app_private_key_string__'):
- try:
- if self.appPrivateKey.startswith('-----BEGIN'):
- setattr(self, '__app_private_key_string__', self.appPrivateKey)
- elif self.app_private_key_path and self.app_private_key_path.startswith('-----BEGIN'):
- setattr(self, '__app_private_key_string__', self.app_private_key_path)
- else:
- with open(self.app_private_key_path) as fp:
- setattr(self, '__app_private_key_string__', fp.read())
- except Exception as e:
- logger.error('{} app_private_key_string exception = {}'.format(repr(self), e.message))
- setattr(self, '__app_private_key_string__', '')
- return getattr(self, '__app_private_key_string__')
- @property
- def public_key_string(self):
- if not hasattr(self, '__public_key_string__'):
- try:
- if self.alipayPublicKey.startswith('-----BEGIN'):
- setattr(self, '__public_key_string__', self.alipayPublicKey)
- elif self.public_key_path.startswith('-----BEGIN'):
- setattr(self, '__public_key_string__', self.public_key_path)
- else:
- with open(self.public_key_path) as fp:
- setattr(self, '__public_key_string__', fp.read())
- except Exception as e:
- logger.error('{} public_key_string exception = {}'.format(repr(self), e.message))
- setattr(self, '__public_key_string__', '')
- return getattr(self, '__public_key_string__')
- @property
- def app_public_key_cert_string(self):
- if not hasattr(self, '__app_public_key_cert_string__'):
- try:
- if self.appPublicKeyCert.startswith('-----BEGIN'):
- setattr(self, '__app_public_key_cert_string__', self.appPublicKeyCert)
- else:
- with open(self.appPublicKeyCert) as fp:
- setattr(self, '__app_public_key_cert_string__', fp.read())
- except Exception as e:
- logger.error('{} app_public_key_cert_string exception = {}'.format(repr(self), e.message))
- setattr(self, '__app_public_key_cert_string__', '')
- return getattr(self, '__app_public_key_cert_string__')
- @property
- def public_key_cert_string(self):
- if not hasattr(self, '__public_key_cert_string__'):
- try:
- if self.publicKeyCert.startswith('-----BEGIN'):
- setattr(self, '__public_key_cert_string__', self.publicKeyCert)
- else:
- with open(self.publicKeyCert) as fp:
- setattr(self, '__public_key_cert_string__', fp.read())
- except Exception as e:
- logger.error('{} public_key_cert_string exception = {}'.format(repr(self), e.message))
- setattr(self, '__public_key_cert_string__', '')
- return getattr(self, '__public_key_cert_string__')
- @property
- def root_cert_string(self):
- if not hasattr(self, '__root_cert_string__'):
- try:
- if self.rootCert.startswith('-----BEGIN'):
- setattr(self, '__root_cert_string__', self.rootCert)
- else:
- with open(self.rootCert) as fp:
- setattr(self, '__root_cert_string__', fp.read())
- except Exception as e:
- logger.error('{} root_cert_string exception = {}'.format(repr(self), e.message))
- setattr(self, '__root_cert_string__', '')
- return getattr(self, '__root_cert_string__')
- @property
- def pay_app_type(self):
- return PayAppType.ALIPAY
- @property
- def __gateway_key__(self):
- _ = [
- self.occupantId,
- self.appid,
- self.occupant.role
- ]
- return APP_KEY_DELIMITER.join(_)
- def to_dict(self, shadow = False):
- return {
- 'id': str(self.id),
- 'appid': self.appid,
- 'hasAlipayAppKeyPair': bool(self.app_private_key_string is not ''),
- 'public_key_path': self.public_key_path,
- 'app_private_key_path': self.app_private_key_path,
- 'alipayPublicKey': encrypt_display(self.alipayPublicKey) if shadow else self.alipayPublicKey,
- 'appPrivateKey': encrypt_display(self.appPrivateKey) if shadow else self.appPrivateKey,
- 'alipayPublicKeyContent': encrypt_display(
- self.alipayPublicKeyContent) if shadow else self.alipayPublicKeyContent,
- 'appPublicKeyContent': encrypt_display(self.appPublicKeyContent) if shadow else self.appPublicKeyContent,
- 'debug': self.debug,
- 'appName': self.appName,
- 'companyName': self.companyName
- }
- @classmethod
- def get_null_app(cls):
- app = cls(appid = '', public_key_path = '', app_private_key_path = '', shadow = False)
- app.enable = False
- app.valid = True
- app.supportWithdraw = False
- app.supportWithdrawBank = False
- return app
- @classmethod
- def __from_gateway_key__(cls, occupant_id, tokens):
- # type: (str, List)->cast(PayAppBase)
- appid = tokens[0]
- app = cls.objects(appid = appid).first() # type: AliApp
- if not app:
- raise UserServerException(u'系统配置错误,请联系平台客服(10005)')
- app.occupantId = occupant_id
- try:
- app.role = tokens[1]
- except:
- app.role = ROLE.agent
- return app
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.ali import AliPayGateway
- return AliPayGateway(self)
- def new_withdraw_gateway(self, gateway_version = None):
- from apps.web.core.payment.ali import AliPayWithdrawGateway
- return AliPayWithdrawGateway(self)
- @property
- def client(self):
- if hasattr(self, '__client__'):
- return getattr(self, '__client__')
- if self.signKeyType not in ['normal', 'cert']:
- raise AliException(
- errCode = -1,
- errMsg = u'参数配置错误',
- client = None)
- params = {
- 'appid': self.appid,
- 'sign_type': "RSA2",
- 'debug': self.debug,
- 'timeout': 15
- }
- params.update({'app_private_key_string': self.app_private_key_string})
- if self.signKeyType == 'normal':
- params.update({'public_key_string': self.public_key_string})
- setattr(self, '__client__', AliPay(**params))
- if self.signKeyType == 'cert':
- params.update({
- 'public_key_cert_string': self.public_key_cert_string,
- 'root_cert_string': self.root_cert_string,
- 'app_public_key_cert_string': self.app_public_key_cert_string,
- 'aes_encrypt_key': self.aes_encrypt_key
- })
- setattr(self, '__client__', DCAliPay(**params))
- return getattr(self, '__client__')
- class PlatformAppBase(PayAppBase):
- """
- 平台记账APP
- """
- meta = {
- 'abstract': True
- }
- APP_NAME = ''
- @property
- def __valid_check__(self):
- return True
- @property
- def __gateway_key__(self):
- _ = [
- settings.MY_PRIMARY_AGENT_ID,
- ROLE.agent
- ]
- return APP_KEY_DELIMITER.join(_)
- @classmethod
- def get_app(cls):
- app = cls()
- app.role = ROLE.agent
- app.occupantId = settings.MY_PRIMARY_AGENT_ID
- app.enable = True
- app.valid = True
- app.inhouse = True
- app.debug = False
- app.companyName = u'武汉大源科技有限公司'
- app.appName = cls.APP_NAME
- return app
- @classmethod
- def __from_gateway_key__(cls, occupant_id, tokens):
- # type: (str, List)->cast(PayAppBase)
- assert occupant_id == settings.MY_PRIMARY_AGENT_ID, u'非法GatewayKey'
- return cls.get_app()
- class PlatformPromotionApp(PlatformAppBase):
- """
- 用于平台记账后 分账给其他人, 例如红包受益 广告收益等 分给经销商
- """
- meta = {
- 'abstract': True
- }
- APP_NAME = u'平台补贴'
- def __repr__(self):
- return '<platform promotion App>'
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.platform import PlatformPromotionPaymentGateway
- return PlatformPromotionPaymentGateway(self, app_platform_type)
- @property
- def pay_app_type(self):
- return PayAppType.PLATFORM_PROMOTION
- class PlatformReconcileApp(PlatformAppBase):
- """
- 平台对账后建立订单分账
- """
- meta = {
- 'abstract': True
- }
- APP_NAME = u'平台对账'
- def __repr__(self):
- return '<platform reconcile App>'
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.platform import PlatformReconcilePaymentGateway
- return PlatformReconcilePaymentGateway(self, app_platform_type)
- @property
- def pay_app_type(self):
- return PayAppType.PLATFORM_RECONCILE
- class PlatformWalletApp(PlatformPromotionApp):
- """
- 用于使用经销商余额(平台资金池)进行SIM卡支付
- """
- meta = {
- 'abstract': True
- }
- def __repr__(self):
- return '<platform wallet App>'
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.platform import PlatformWalletPaymentGateway
- return PlatformWalletPaymentGateway(self, app_platform_type)
- @property
- def pay_app_type(self):
- return PayAppType.PLATFORM_WALLET
- class LedgerConsumeApp(PayAppBase):
- meta = {
- 'abstract': True
- }
- APP_NAME = u'消费分账'
- @property
- def pay_app_type(self):
- return PayAppType.LEDGER_CONSUME
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.consume import LedgerConsumePaymentGateway
- return LedgerConsumePaymentGateway(self, app_platform_type)
- @classmethod
- def get_app(cls, dealer): # type:(Dealer) -> LedgerConsumeApp
- app = cls()
- app.role = ROLE.agent
- app.occupantId = settings.MY_PRIMARY_AGENT_ID
- app.enable = True
- app.valid = True
- app.inhouse = True
- app.debug = False
- app.appName = cls.APP_NAME
- return app
- class ManualPayApp(PayAppBase):
- """
- 客户线下打款后, 超级管理员建立充值记录
- """
- def __repr__(self):
- return '<manual pay App>'
- @property
- def __valid_check__(self):
- return True
- def new_gateway(self, app_platform_type):
- from apps.web.core.payment.manual import ManualPaymentGateway
- return ManualPaymentGateway(self, app_platform_type)
- @property
- def pay_app_type(self):
- return PayAppType.MANUAL
- @property
- def __gateway_key__(self):
- _ = [
- self.occupantId,
- self.occupant.role
- ]
- return APP_KEY_DELIMITER.join(_)
- @classmethod
- def get_null_app(cls):
- app = cls()
- app.enable = True
- app.valid = True
- return app
- @classmethod
- def __from_gateway_key__(cls, occupant_id, tokens):
- # type: (str, List)->cast(PayAppBase)
- app = cls.get_null_app() # type: ManualPayApp
- app.occupantId = occupant_id
- try:
- app.role = tokens[1]
- except:
- app.role = ROLE.agent
- return app
- class WechatComponentApp(DynamicDocument):
- appid = StringField(verbose_name='appid', default='', required=True)
- secret = StringField(verbose_name="secretId", default='', required=True)
- aesKey = StringField(verbose_name="aesKey", default='', required=True)
- token = StringField(verbose_name="token", default='', required=True)
- companyName = StringField(verbose_name="companyName", default='')
- appName = StringField(verbose_name="appName", default='')
- meta = {
- 'collection': 'wechat_component_app'
- }
- class WechatAuthorizer(DynamicDocument):
- appid = StringField(verbose_name='appid', default='', required=True)
- appType = IntField(verbose_name='appType(0-mini,1-biz)', default=1)
- serviceType = IntField(verbose_name='serviceType', default=2)
- verifyInfo = IntField(verbose_name='verifyInfo')
- nickName = StringField(verbose_name='appType', default='')
- userName = StringField(verbose_name='appType', default='')
- headImg = StringField(verbose_name='appType', default='')
- qrcodeUrl = StringField(verbose_name='appType', default='')
- principalName = StringField(verbose_name='appType', default='')
- funcList = ListField(verbose_name='funcList', default=[])
- appStatus = IntField(verbose_name='appStatus')
- extra = DictField(verbose_name='extra', default={})
- refreshToken = StringField(verbose_name='appType', default='')
- updateTime = DateTimeField(default=datetime.datetime.now, verbose_name=u'授权时间')
- dtAdded = DateTimeField(default=datetime.datetime.now, verbose_name=u'授权时间')
- authState = StringField(verbose_name='authState', default='authorized')
- meta = {
- 'collection': 'wechat_authorizer'
- }
- REFRESH_TOKEN_KEY = 'refresh_token_{}'
- @classmethod
- def getAuthRecord(cls, appid):
- token = serviceCache.get(cls.REFRESH_TOKEN_KEY.format(appid))
- if not token:
- authorizer = cls.objects(appid=appid).first()
- if authorizer:
- token = authorizer.refreshToken
- serviceCache.set(cls.REFRESH_TOKEN_KEY.format(settings.WECHAT_3RD_APPID), token)
- return token
- @classmethod
- def createOrUpdateAuthRecord(cls, payload):
- payload.update({
- 'authState': 'authorized',
- 'updateTime': datetime.datetime.now()
- })
- cls.objects(appid=payload['appid']).update_one(upsert=True, **payload)
- @classmethod
- def deleteAuthRecord(cls, appid):
- cls.objects(appid=appid).update_one(
- appid=appid, authState='unauthorized', updateTime=datetime.datetime.now())
- class WithdrawEntity(EmbeddedDocument):
- wechatWithdrawApp = LazyReferenceField(document_type = WechatPayApp, default = None)
- alipayWithdrawApp = LazyReferenceField(document_type = AliApp, default = None)
- @property
- def alipay_app(self):
- if self.alipayWithdrawApp:
- return self.alipayWithdrawApp.fetch()
- else:
- return None
- @property
- def wechat_app(self):
- if self.wechatWithdrawApp:
- return self.wechatWithdrawApp.fetch()
- else:
- return None
- class BoundOpenInfo(EmbeddedDocument):
- openId = StringField(required = True, default = '')
- avatar = StringField(default = '')
- sex = IntField(verbose_name = "性别", default = Const.USER_SEX.UNKNOWN)
- nickname = StringField(verbose_name = "昵称", default = "")
- def to_dict(self):
- return {
- 'openId': self.openId
- }
- def to_detail_dict(self):
- return {
- "openId": self.openId,
- "avatar": self.avatar,
- "sex": self.sex,
- "nickname": self.nickname
- }
- class OfflineTask(Searchable):
- _STATUS_MAP = Const.CELERY_TASK_RESULT_TRANSLATION
- name = StringField(verbose_name = '任务名称')
- type = StringField(verbose_name = '类型')
- status = StringField(verbose_name = '状态', default = 'PENDING')
- result = StringField(verbose_name = '执行结果详细信息', default = '')
- link = StringField(verbose_name = '链接')
- celery_task_id = StringField(verbose_name = 'celery侧uuid', default = '')
- process_func_name = StringField(verbose_name = '任务的函数名')
- role = StringField(verbose_name = '角色', choices = ROLE.choices())
- ownerId = ObjectIdField(verbose_name = '创建者ID')
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '任务生成时间')
- finishedTime = DateTimeField(verbose_name = '完成时间', default = None)
- search_fields = ('id', 'celery_task_id', 'name')
- meta = {
- 'collection': 'offline_task',
- 'db_alias': 'logdata',
- 'indexes': [
- {
- 'fields': ['ownerId', 'role']
- }
- ],
- }
- def to_dict(self):
- return {
- 'id': str(self.id),
- 'name': self.name,
- 'status': self.status,
- 'result': self.result,
- 'link': self.link,
- 'celery_task_id': self.celery_task_id,
- 'type': self.type,
- 'startTime': self.dateTimeAdded,
- 'finishedTime': self.finishedTime
- }
- @classmethod
- def issue_export_report(cls, offline_task_name, process_func_name, task_type, userid, role, ext = 'xlsx'):
- file_path = '{}reports/{}/{}.{}'.format(
- settings.MEDIA_URL,
- datetime.datetime.now().strftime("%Y%m%d"),
- offline_task_name,
- ext)
- if settings.UPLOAD_REPORT_TO_OSS:
- link = '{}{}'.format(settings.OSS_RESOURCE_URL, file_path)
- else:
- link = '{}{}?local=true'.format(settings.LOCAL_REPORT_URL, file_path)
- offline_task = OfflineTask(
- name = offline_task_name,
- type = task_type,
- process_func_name = process_func_name,
- ownerId = userid,
- role = role,
- link = link)
- offline_task.save()
- return file_path[1:], offline_task
- @classmethod
- def issue(cls, offline_task_name, process_func_name, task_type, userid, role):
- offline_task = OfflineTask(
- name = offline_task_name,
- type = task_type,
- process_func_name = process_func_name,
- ownerId = userid,
- role = role)
- offline_task.save()
- return offline_task
- class BankCard(Searchable):
- """
- TODO: 已经废弃.升级脚本暂时需要, 下个版本后删除
- """
- class AccountType(object):
- PERSONAL = 'personal'
- PUBLIC = 'public'
- bankName = StringField(verbose_name = u"银行名称", required = True)
- branchName = StringField(verbose_name = u"支行信息")
- cardNo = StringField(verbose_name = u"银行卡号", required = True, unique = True)
- cardType = StringField(verbose_name = u"卡类型", default = "debit")
- remark = StringField(verbose_name = u"备注", default = "")
- # category = StringField(verbose_name=u"所属类别,个人或者公司", default='individual')
- province = StringField(verbose_name = u"省级别开户地址信息", default = "")
- city = StringField(verbose_name = u"市级别开户地址信息", default = "")
- district = StringField(verbose_name = u"区级别开户地址信息", default = "")
- holderName = StringField(verbose_name = u"开户人姓名", required = True)
- code = StringField(verbose_name = u"微信支持银行卡所对应的code(银行编号), 默认为0000表示不支持", default = "0000")
- cnapsCode = StringField(verbose_name = u'支行联行号', default = '')
- is_valid = BooleanField(verbose_name = u"是否是有效卡", default = True)
- is_primary = BooleanField(verbose_name = u"优先选择", default = True)
- manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False)
- accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = AccountType.PERSONAL)
- meta = {"collection": "bankcards", "db_alias": "default"}
- class SystemSettings(Searchable):
- key = StringField(verbose_name = u'设置名称')
- value = DynamicField(verbose_name = u'设置参数')
- desc = StringField(verbose_name = u'设置描述')
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加时间')
- meta = {
- 'collection': 'system_settings'
- }
- all_settings = {}
- load_ready = False
- @classmethod
- def get_system_setting(cls, setting_name, default = None):
- if not cls.load_ready:
- cls.load_settings()
- return cls.all_settings.get(setting_name, default)
- @classmethod
- def load_settings(cls):
- cls.all_settings = {item.key: item.value for item in cls.objects.all()}
- cls.load_ready = True
- @classmethod
- def reload_settings(cls):
- logger.debug("reload system settings.")
- cls.load_ready = False
- cls.load_settings()
- @classmethod
- def set_setting(cls, key, value):
- cls.objects(key=key).update_one(upsert=True, **{"value": value})
- cls.all_settings[key] = value
- @classmethod
- def set_mem_setting(cls, key, value):
- cls.all_settings[key] = value
- @classmethod
- def get_support_redpack_list(cls):
- obj = cls.objects.filter(key='SUPPORT_REDPACK_LIST').first()
- if obj:
- return json_loads(obj.value)
- else:
- return []
- @classmethod
- def set_support_redpack_list(cls, dataList):
- value = json_dumps(dataList)
- return cls.objects.filter(key='SUPPORT_REDPACK_LIST').update(value=value)
- @classmethod
- def disable_alipay_ruhui(cls):
- item = cls.objects.filter(key='DISABLE_REDPACK').first()
- if not item:
- vaule = {}
- else:
- vaule = item.value
- return vaule.get('RUHUI', False)
- @classmethod
- def disable_alipay_laxin(cls):
- item = cls.objects.filter(key='DISABLE_REDPACK').first()
- if not item:
- vaule = {}
- else:
- vaule = item.value
- return vaule.get('LAXIN', False)
- @classmethod
- def get_system_setting_direct(cls, setting_name, default = None):
- obj = cls.objects.filter(key = setting_name).first()
- if obj:
- return obj.value
- else:
- return default
- class DriverCode(Searchable):
- code = StringField(verbose_name = u'驱动编码', unique = True)
- name = StringField(verbose_name = u'驱动名称', unique = True)
- description = StringField(verbose_name = u'驱动描述')
- createdTime = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间')
- adapterFile = StringField(verbose_name = '业务处理文件名称', default = '')
- adapterVer = StringField(verbose_name = '业务版本', default = '2.0') # 发生变化后,会自动化加载
- adapter = StringField(verbose_name = '适配器类名称', default = '')
- eventerFile = StringField(verbose_name = '事件处理文件名称', default = '')
- eventerVer = StringField(verbose_name = '事件处理版本', default = '2.0') # 发生变化后,会自动化加载
- features = DictField(verbose_name = "额外特征", default = {}) # 属于某个设备类型的特征
- meta = {'collection': 'device_driver_code', 'db_alias': 'default'}
- def __repr__(self):
- return '<DriverCode code=%s>' % (self.code,)
- def to_dict(self):
- return {
- 'code': self.code,
- 'name': self.name,
- 'features': self.features,
- 'description': self.description
- }
- @classmethod
- def get_driver_code(cls, code):
- # type: (str)->dict
- cache_key = lambda code: 'driverCode{}'.format(code)
- if not code: return None
- driverInfo = cache.get(cache_key(code))
- if driverInfo:
- return driverInfo
- else:
- obj = cls.objects(code = code).first() # type: DriverCode
- if obj:
- value = {
- 'id': str(obj.id),
- 'code': obj.code,
- 'name': obj.name,
- 'description': obj.description,
- 'createdTime': obj.to_datetime_str(obj.createdTime),
- 'adapterFile': obj.adapterFile,
- 'eventerFile': obj.eventerFile,
- 'features': dict(obj.features)
- }
- cache.set(cache_key(code), value)
- return value
- return None
- class DriverAdapter(DynamicDocument):
- adapterFile = StringField(verbose_name = '业务处理文件名称', unique = True)
- adapterVer = StringField(verbose_name = '业务版本', default = '1.0')
- adapter = StringField(verbose_name = '适配器类名称', default = '')
- meta = {'collection': 'device_driver_adapter', 'db_alias': 'default'}
- _lock = ThreadLock()
- adapters = {}
- def to_dict(self):
- return {
- 'adapterFile': self.adapterFile,
- 'adapterVer': self.adapterVer,
- 'adapter': self.adapter
- }
- @classmethod
- def get_driver_adapter(cls, code, dev, online = True):
- cache_key = lambda filename: 'driverAdapter{}'.format(filename)
- adapter_file = None
- if not online:
- adapter_file = 'offline'
- else:
- driver_code_info = DriverCode.get_driver_code(code)
- if driver_code_info:
- adapter_file = driver_code_info['adapterFile']
- if not adapter_file:
- adapter_file = 'commonPulse'
- adapter_info = cache.get(cache_key(adapter_file))
- if not adapter_info:
- adapter = cls.objects.get(adapterFile = adapter_file)
- adapter_info = {
- 'adapterFile': adapter.adapterFile,
- 'adapterVer': adapter.adapterVer,
- 'adapter': adapter.adapter
- }
- cache.set(cache_key(adapter_file), {
- 'adapterFile': adapter_info['adapterFile'],
- 'adapterVer': adapter_info['adapterVer'],
- 'adapter': adapter_info['adapter']
- })
- logger.debug('[cache] load driver adapter info is: {}'.format(adapter_info))
- mem_info = cls.adapters.get(adapter_file, None)
- if mem_info and 'module' in mem_info and \
- mem_info['adapterVer'] == adapter_info['adapterVer'] and \
- mem_info['adapterFile'] == adapter_info['adapterFile'] and \
- mem_info['adapter'] == adapter_info['adapter']:
- adapter_module = cls.adapters[adapter_file]['module']
- logger.debug('adapter module {} has loaded.'.format(adapter_module))
- else:
- try:
- cls._lock.acquire_lock()
- mem_info = cls.adapters.get(adapter_file, None)
- if mem_info and 'module' in mem_info and \
- mem_info['adapterVer'] == adapter_info['adapterVer'] and \
- mem_info['adapterFile'] == adapter_info['adapterFile'] and \
- mem_info['adapter'] == adapter_info['adapter']:
- adapter_module = cls.adapters[adapter_file]['module']
- logger.debug('adapter module {} has loaded.'.format(adapter_module))
- else:
- if adapter_file in cls.adapters:
- mem_info = cls.adapters[adapter_file]
- logger.info('device adapter changed, need reload, oldInfo=%s, newInfo=%s' % (
- mem_info, adapter_info))
- module_name = 'apps.web.core.adapter.{}'.format(adapter_info['adapterFile'])
- adapter_module = import_module(module_name)
- logger.info('finished import new module = {}'.format(adapter_module))
- adapter_module = eval('reload({})'.format(module_name))
- logger.info('finished reload new module = {}'.format(adapter_module))
- adapter_info.update({'module': adapter_module})
- cls.adapters[adapter_file] = adapter_info
- else:
- logger.info('loading device adapter, adapter info = {}'.format(adapter_info))
- module_name = 'apps.web.core.adapter.{}'.format(adapter_file)
- adapter_module = import_module(module_name)
- logger.info('finished import new module = {}'.format(adapter_module))
- adapter_info.update({'module': adapter_module})
- cls.adapters[adapter_file] = adapter_info
- finally:
- cls._lock.release_lock()
- return eval('adapter_module.%s(dev)' % (adapter_info['adapter']))
- class DriverEventer(DynamicDocument):
- eventerFile = StringField(verbose_name = '事件处理文件名称', unique = True)
- eventerVer = StringField(verbose_name = '事件处理版本', default = '1.0') # 发生变化后,会自动化加载
- meta = {'collection': 'device_driver_eventer', 'db_alias': 'default'}
- _lock = ThreadLock()
- eventers = {}
- def to_dict(self):
- return {
- 'id': str(self.id),
- 'eventerFile': self.eventerFile,
- 'eventerVer': self.eventerVer
- }
- @classmethod
- def get_driver_eventer(cls, code, device_adapter, online = True):
- cache_key = lambda filename: 'driverEventer{}'.format(filename)
- eventer_file = None
- if not online:
- eventer_file = 'offline'
- else:
- driver_code_info = DriverCode.get_driver_code(code)
- if driver_code_info:
- eventer_file = driver_code_info['eventerFile']
- if not eventer_file:
- if device_adapter.__class__.__name__ == 'CommonPulseAdapter':
- eventer_file = 'commonPulse'
- else:
- eventer_file = 'dummy'
- eventer_info = cache.get(cache_key(eventer_file))
- if not eventer_info:
- eventer = cls.objects.get(eventerFile = eventer_file)
- eventer_info = {'eventerFile': eventer.eventerFile, 'eventerVer': eventer.eventerVer}
- logger.debug('load driver eventer info is: {}'.format(eventer_info))
- cache.set(cache_key(eventer_file),
- {
- 'eventerFile': eventer_info['eventerFile'],
- 'eventerVer': eventer_info['eventerVer']
- })
- mem_info = cls.eventers.get(eventer_file, None)
- if mem_info and 'module' in mem_info and mem_info['eventerVer'] == eventer_info['eventerVer'] and \
- mem_info['eventerFile'] == eventer_info['eventerFile']:
- eventer_module = mem_info['module']
- logger.debug('eventer module {} has loaded.'.format(eventer_module))
- else:
- try:
- cls._lock.acquire_lock()
- mem_info = cls.eventers.get(eventer_file, None)
- if mem_info and 'module' in mem_info and mem_info['eventerVer'] == eventer_info['eventerVer'] and \
- mem_info['eventerFile'] == eventer_info['eventerFile']:
- eventer_module = mem_info['module']
- logger.debug('eventer module {} has loaded.'.format(eventer_module))
- else:
- if mem_info:
- logger.info(
- 'device eventer changed, need reload, oldInfo = %s, newInfo = %s' % (
- mem_info, eventer_info))
- module_name = 'apps.web.eventer.{}'.format(eventer_file)
- eventer_module = import_module(module_name)
- logger.info('finished import new eventer module = {}'.format(eventer_module))
- eventer_module = eval('reload({})'.format(module_name))
- logger.info('finished reload new eventer module = {}'.format(eventer_module))
- eventer_info.update({'module': eventer_module})
- cls.eventers[eventer_file] = eventer_info
- else:
- logger.info('loading device eventer, eventer info = {}'.format(eventer_info))
- module_name = 'apps.web.eventer.{}'.format(eventer_file)
- eventer_module = import_module(module_name)
- logger.info('finished import new eventer module = {}'.format(eventer_module))
- eventer_info.update({'module': eventer_module})
- cls.eventers[eventer_file] = eventer_info
- finally:
- cls._lock.release_lock()
- return eval('eventer_module.builder(device_adapter)')
- class CustomerPayRelation(Searchable):
- """
- app 和使用者的关联表
- 为 多对多的关系
- """
- ownerId = StringField(verbose_name=u"持有者ID")
- ownerRole = StringField(verbose_name=u"持有者角色")
- appId = StringField(verbose_name=u"app的ID")
- appType = StringField(verbose_name=u"支付APP的种类")
- active = BooleanField(verbose_name=u"是否处于使用状态", default=False)
- isDelete = BooleanField(verbose_name=u"是否已经删除", default=False)
- @classmethod
- def add_relation(cls, customer, app):
- rel = cls.objects.filter(ownerId=str(customer.id), appId=str(app.id))
- if not rel:
- rel = cls()
- rel.ownerId = str(customer.id)
- rel.ownerRole = str(customer.role)
- rel.appId = str(app.id)
- rel.appType = str(app.__class__.__name__)
- return rel.save()
- else:
- return rel
- def active_app(self):
- """
- 激活app 使之处于可用状态
- """
- self.active = True
- return self.save()
- def deactive_app(self):
- """
- 关闭app 使之处于不可用状态
- """
- self.active = False
- return self.save()
- def delete_relation(self):
- """
- 删除app
- """
- app = self.deactive_app()
- app.isDelete = True
- return app.save()
|