# -*- 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.merchant.models import MerchantSourceInfo, JDOpenApplyInfo 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 WechatMiniApp(EmbeddedApp): mchid = StringField(verbose_name = 'mchid', null = False) apikey = StringField(verbose_name = 'apikey', null = False) sslcert_path = StringField(verbose_name = u'PERM证书路径', null = False) sslkey_path = StringField(verbose_name = u'PKCS8 PERM私钥路径', null = False) sslCert = StringField(verbose_name = u'PERM证书内容', default = '') sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '') manual_withdraw = BooleanField(verbose_name = u'是否手动提现', default = False) debug = BooleanField(verbose_name = u'是否测试账号', default = False) def __repr__(self): return '' % (self.appid, self.mchid) @property def __valid_check__(self): return super(WechatMiniApp, self).__valid_check__ and bool( self.mchid and self.apikey and self.ssl_cert and self.ssl_key) @property def pay_app_type(self): return PayAppType.WECHAT_MINI @property def __gateway_key__(self): _ = [ self.occupantId, self.appid, self.mchid, self.occupant.role ] return APP_KEY_DELIMITER.join(_) def to_dict(self): return { 'appid': self.appid, 'secret': self.secret, 'mchid': self.mchid, 'apikey': self.apikey, 'debug': self.debug, 'name': self.name, 'companyName': self.companyName } @classmethod def get_null_app(cls): app = cls(appid = '', secret = '', mchid = '', apikey = '', sslcert_path = '', sslkey_path = '') app.enable = False app.valid = True return app @classmethod def __from_gateway_key__(cls, occupant_id, tokens): # type: (str, List)->cast(PayAppBase) appid, mchid = tokens[0], tokens[1] # TODO 这个地方确实没法解决. 必须通过AGENT来加载 Agent = import_string('apps.web.agent.models.Agent') agent = Agent.objects(id = str(occupant_id)).first() # type: Agent if not agent: raise UserServerException(u'系统配置错误(第三方支付)') app = agent.wechatMiniApp # type: WechatMiniApp if not app or not app.valid: raise UserServerException(u'系统配置错误,请联系平台客服') app.occupantId = occupant_id app.occupant = agent return app def new_gateway(self, app_platform_type): from apps.web.core.payment.wechat import WechatPaymentGateway return WechatPaymentGateway(self, app_platform_type) def new_auth_bridge(self): from apps.web.core.auth.wechat import WechatAuthBridge return WechatAuthBridge(app = self) @property def ssl_cert(self): if not hasattr(self, '__ssl_cert__'): 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()) return str(getattr(self, '__ssl_cert__')) @property def ssl_key(self): if not hasattr(self, '__ssl_key__'): 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()) return str(getattr(self, '__ssl_key__')) class WechatAuthApp(EmbeddedApp): def __repr__(self): return '' % (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 = "微信推送消息模版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' } }) }, '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 = "微信推送消息模版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 = "微信推送消息模版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 JDAuthApp(EmbeddedApp): def __repr__(self): return '' % (self.appid,) def to_dict(self): return { 'appid': self.appid, 'secret': self.secret, 'name': self.name, 'companyName': self.companyName } class MerchantAgent(Searchable): """ 将商户号 和 代理商账号绑定起来 这样方便 代理商收取手续费的时候 直接从相应的网关获取 """ agentNo = StringField(verbose_name=u"商户号的服务商商户号") accessKey = StringField(verbose_name=u"accessKey", default=None) secretKey = StringField(verbose_name=u"secretKey", default=None) dateTimeAdded = DateTimeField(verbose_name=u"添加进来的时间") remarks = StringField(verbose_name=u"备注", default=u"") agentSystemId = StringField(verbose_name=u"服务商的systemId(京东聚合老系统需要)", default=None) def __str__(self): return self.agentNo @classmethod def get_agent_id(cls, agentSystemId): merchantAgent = cls.objects.filter(agentSystemId=agentSystemId).first() or cls() return merchantAgent.agentId @classmethod def default_merchant_agent(cls): wflSystemId = os.environ.get("JD_SYSTEM_ID") or "112122276PSP" return cls.objects.filter(agentSystemId=wflSystemId).first() 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) functions = ListField(verbose_name = u'支持功能列表', default = [Function.PAY]) @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.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, is_ledger = True, gateway_version = None): raise NotImplementedError('must implement new_withdraw_gateway') @property def split_id(self): raise NotImplementedError('must implement split_id') def refund_bill_split_list(self, partition_map): raise NotImplementedError('must implement refund_bill_split_list') def bill_split_rule(self, partition_map): raise NotImplementedError('must implement refund_bill_split_list') class JosShopInfo(EmbeddedDocument): """ 京东云支付的相关信息 其中门店ID很关键 """ # 品牌ID 查看方式 品牌管理 -> 品牌认证 -> 品牌ID brandId = StringField(verbose_name = u"京东引流 品牌ID", default = "13499") # 企业BID 查看方式 品牌管理 -> 企业认证 -> 企业BID bizId = StringField(verbose_name = u"京东引流 企业BID", default = "100000000006317") # 外部门店ID 重要 # 这个门店的编号直接影响用户的欠款去向 千万注意 # jos后台的逻辑是 通过 exStoreId 查找到 # 绑定的京东商户, 通过京东商户找到绑定的钱包 最后用户付款的钱直接到对应钱包里面 exStoreId = StringField(verbose_name = u"京东引流的外部门店ID,需要web端操作绑定", required = True) # 和门店相关联的商品的ID 商品发布后一定需要关联门店,可以多个门店关联一个商品 也可以一个商品关联多个门店 exSkuId = StringField(verbose_name = u"京东引流的外部商品ID,需要web端操作绑定", default = "100001") brandName = StringField(verbose_name = u"品牌名称, 需要和品牌ID对应", default = u"微付乐") tradeName = StringField(verbose_name = u"商品名称,需要和商品ID对应", default = u"测试商品") 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 = '') apikey = StringField(verbose_name = u'apikey', null = False) sslCert = StringField(verbose_name = u'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) # 暂时保留 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 = '') withdrawV3 = BooleanField(verbose_name = u'是否使用V3接口对微信转账', default = False) manual_withdraw = BooleanField(verbose_name = u'是否手动提现', default = False) functions = ListField(verbose_name = u'支持功能列表', default = [PayAppBase.Function.PAY, PayAppBase.Function.WITHDRAW]) search_fields = ('appid', 'mchid', 'name', 'remarks') meta = { 'indexes': [ { 'fields': ['appid', 'mchid'], 'unique': True }, ], 'collection': 'wechat_pay_app', 'db_alias': 'default' } def __repr__(self): return ''.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 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, is_ledger = True, gateway_version = 'v1'): from apps.web.core.payment.wechat import WechatWithdrawGateway if not self.enable: is_ledger = False if gateway_version == 'v3': if not self.withdrawV3 or not self.apikey_v3: return None return WechatWithdrawGateway(self, gateway_version = gateway_version, is_ledger = is_ledger) @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): 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 = '') # 公钥模式 app_private_key_path = StringField(verbose_name = u'支付宝应用私钥路径', default = '') appPrivateKey = StringField(verbose_name = u'支付宝应用私钥. 只有测试情况下才能使用字符串', default = '') public_key_path = StringField(verbose_name = u'支付宝账户公钥路径', default = '') alipayPublicKey = StringField(verbose_name = u'文本形式支付宝账户公钥', default = '') # 证书模式 app_publickey_cert_path = StringField(verbose_name = u'支付宝应用公共证书路径', default = '') appPublicKeyCert = StringField(verbose_name = u'支付宝应用公共证书', default = '') publickey_cert_path = StringField(verbose_name = u'支付宝公共证书路径', default = '') publicKeyCert = StringField(verbose_name = u'支付宝公共证书', default = '') root_cert_path = StringField(verbose_name = u'支付宝根证书路径', default = '') rootCert = StringField(verbose_name = u'支付宝根证书', default = '') signKeyType = StringField(verbose_name = u'签名方式(normal,cert)', default = 'normal') # 中间字段 alipayPublicKeyContent = StringField(verbose_name = u'支付宝公钥文本', default = '') appPublicKeyContent = StringField(verbose_name = u'应用公钥文本', default = '') search_fields = ('appid', 'name', 'remarks') meta = { 'indexes': [ { 'fields': ['appid'], 'unique': True }, ], 'collection': 'ali_pay_app', 'db_alias': 'default' } def __repr__(self): return ''.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 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, is_ledger = True, gateway_version = None): from apps.web.core.payment.ali import AliPayWithdrawGateway if not self.enable: return AliPayWithdrawGateway(self, False) else: return AliPayWithdrawGateway(self, is_ledger) @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 '' 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 '' 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 '' 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 JDOpenPayApp(PayAppBase): customerNum = StringField(verbose_name = u'商户编号', unique = True, required = True, null = False) shopNum = StringField(verbose_name = u'店铺编号', required = True, null = False) accessKey = StringField(verbose_name=u"accessKey", default=None) secretKey = StringField(verbose_name=u"secretKey", default=None) agent = LazyReferenceField(document_type=MerchantAgent) meta = { 'indexes': [ { 'fields': ['customerNum'], 'unique': True }, { 'fields': ['wxSubMchId'] } ], 'collection': 'jdopen_pay_app', 'db_alias': 'default' } def __repr__(self): return ''.format(self.customerNum, self.shopNum) @property def __valid_check__(self): # return bool(self.customerNum) and bool(self.shopNum) return bool(self.customerNum) @property def pay_app_type(self): return PayAppType.JD_OPEN @property def __gateway_key__(self): _ = [ self.occupantId, self.customerNum, self.occupant.role ] return APP_KEY_DELIMITER.join(_) @classmethod def get_null_app(cls): app = cls(merchant_no = '') app.enable = False app.valid = True return app @classmethod def __from_gateway_key__(cls, occupant_id, tokens): # type: (str, List)->cast(PayAppBase) customerNum = tokens[0] app = cls.objects(customerNum = customerNum).first() # type: JDOpenPayApp if not app: raise UserServerException(u'系统配置错误,请联系平台客服') 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.jdopen import JDOpenPaymentGateway return JDOpenPaymentGateway(self, app_platform_type) def to_dict(self, shadow = True): return { 'id': str(self.id), 'customerNum': self.customerNum, 'companyName': self.companyName, 'appName': self.appName, 'remarks': self.remarks } @classmethod def get_or_create_by_merchant(cls, source): # type: (JDOpenApplyInfo) -> JDOpenPayApp app = cls.objects.filter(agentNum=source.agentNum, customerNum=source.customerNum) if not app: app = cls(agentNum=source.agentNum, customerNum=source.customerNum) app.settleNum = source.settleNum app.shopNum = source.shopNum app.accessKey, app.secretKey = source.get_access_and_secret() return app.save() @property def split_id(self): return self.customerNum def bill_split_rule(self, partition_map): """ 分账list必须全部包括收款方和分账方,分账金额保留两位小数,且分账金额相加需等于订单金额 :return: """ partitions = list(flatten(partition_map.values())) rv = [] for partition in partitions: if RMB(partition['money']) > RMB(0): rv.append({ 'customerNum': partition['merchantId'], 'amount': str(RMB(partition['money'])) }) if len(rv) <= 1: return None, None, None else: return 'FIXED', 'RECEIVER', rv def refund_bill_split_list(self, partition_map): """ 京东退款分账规则: 1、即使退款为0, 也必须带上拉起支付的商户号 2、所以参与支付分账的商户, 即使金额为0, 也必须带上 :return: """ ownerPartition = partition_map.get(PARTITION_ROLE.OWNER)[0] if 'merchantId' not in ownerPartition: return None partitions = list(flatten(partition_map.values())) rv = [] for partition in partitions: if 'merchantId' in partition: rv.append({ 'customerNum': partition['merchantId'], 'amount': str(abs(RMB(partition['money']))) }) if len(rv) <= 1: return None else: return rv class ManualPayApp(PayAppBase): """ 客户线下打款后, 超级管理员建立充值记录 """ def __repr__(self): return '' @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 SwapPayApp(PayAppBase): """ 互联互通支付APP """ def __repr__(self): return '' @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.SWAP @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: SwapPayApp 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 '' % (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 WechatServiceProvider(Searchable): mchid = StringField(verbose_name = u'商户号', null = False) apikey = StringField(verbose_name = u'api秘钥', default = '') apiclient_cert_path = StringField(verbose_name = u'api证书路径', default = '') apiclient_key_path = StringField(verbose_name = u'api证书秘钥路径', default = '') apiclient_serial_number = StringField(verbose_name = u'api证书序列号', default = '') sslCert = StringField(verbose_name = u'PERM证书内容', default = '') sslKey = StringField(verbose_name = u'PKCS8 PERM私钥内容', default = '') # v3的 apiKeyV3 = StringField(verbose_name = u'APIv3秘钥', default = '', db_field = "apikey_v3") sslCertV3 = StringField(verbose_name = u"平台证书 用来加密敏感信息使用") search_fields = ('mchid') meta = { 'indexes': [ { 'fields': ['mchid'], 'unique': True }, ], 'collection': 'wechat_service_provider', 'db_alias': 'default' } @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.apiclient_cert_path.startswith('-----BEGIN'): setattr(self, '__ssl_cert__', self.apiclient_cert_path) else: with open(self.apiclient_cert_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.apiclient_key_path.startswith('-----BEGIN'): setattr(self, '__ssl_key__', self.apiclient_key_path) else: with open(self.apiclient_key_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__') 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()