1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308 |
- # -*- 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
- agent = dealer.productAgent
- app = cls()
- app.role = ROLE.agent
- app.occupantId = str(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()
|