1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import logging
- import warnings
- from bson import ObjectId
- from django.conf import settings
- from django.contrib.auth.hashers import make_password, check_password
- from mongoengine import StringField, DateTimeField, DynamicDocument, ListField, IntField, BooleanField, DictField, \
- ObjectIdField, EmbeddedDocument, EmbeddedDocumentListField
- from typing import Optional, List, Dict, TYPE_CHECKING
- from apilib.monetary import RMB, sum_rmb, AccuracyRMB
- from apilib.systypes import IterConstant
- from apilib.utils_datetime import to_datetime, get_zero_time
- from apilib.utils_json import json_dumps, json_loads
- from apilib.utils_sys import memcache_lock, ThreadLock
- from apps.web import district
- from apps.web.agent.define import AGENT_INCOME_TYPE
- from apps.web.common.transaction import WithdrawStatus, WITHDRAW_PAY_TYPE, WithdrawHandler, OrderNoMaker, OrderMainType, \
- RefundSubType
- from apps.web.constant import Const
- from apps.web.core import ROLE
- from apps.web.core.db import Searchable, StrictDictField, MonetaryField, RoleBaseDocument, PermillageField, \
- AccuracyMoneyField, BooleanIntField, BaseDocument
- from apps.web.core.exceptions import ImproperlyConfigured
- from apps.web.core.payment import WithdrawGateway, PaymentGateway
- from apps.web.dealer.define import DEALER_INCOME_TYPE
- from apps.web.utils import LimitAttemptsManager
- from library.misc import BankAPI
- if TYPE_CHECKING:
- from apps.web.core.messages.sms import WithdrawSMSProvider
- from pymongo.results import UpdateResult
- from apps.web.device.models import DeviceDict, GroupDict
- from apps.web.core import PayAppBase
- from apps.web.user.models import RechargeRecord
- from apps.web.dealer.models import DealerRechargeRecord
- logger = logging.getLogger(__name__)
- class District(object):
- memDict = {}
- @staticmethod
- def load_in_mem(dict_, parentId = None):
- for value in dict_:
- if value.has_key('children'):
- District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId}
- District.load_in_mem(value['children'], value['value'])
- else:
- District.memDict[value['value']] = {'text': value['text'], 'parentId': parentId}
- @staticmethod
- def get_district(districtId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- textList = []
- area = District.memDict.get(districtId, {})
- while area.get('parentId'):
- textList.append(area.get('text', ''))
- area = District.memDict.get(area.get('parentId', {}))
- textList.append(area.get('text', ''))
- return ' '.join(textList[::-1])
- @staticmethod
- def get_area(districtId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- area = District.memDict.get(districtId, {})
- return area.get('text')
- @staticmethod
- def Is_include(bigId, smallId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- if bigId == smallId:
- return True
- parentId = District.memDict.get(smallId).get('parentId')
- while parentId is not None:
- if parentId == bigId:
- return True
- parentId = District.memDict.get(parentId).get('parentId')
- return False
- @staticmethod
- def get_subIds(districtId):
- if not District.memDict:
- District.load_in_mem(district.DISTRICT)
- result = []
- for k, v in District.memDict.items():
- if v['parentId'] == districtId:
- result.append(k)
- return result
- # DeviceWarningField = namedtuple("DeviceWarningField", "warningPart warningStatus warningTime warningDesc warningUart")
- #
- #
- # class DeviceWarning(DeviceWarningField):
- #
- # def to_cache_dict(self):
- # _key = self.warningPart
- # _value = {
- # "warningStatus": self.warningStatus,
- # "warningTime": self.warningTime,
- # "warningDesc": self.warningDesc,
- # "warningUart": self.warningUart
- # }
- #
- # return {_key: _value}
- #
- # @classmethod
- # def create(cls, **kwargs):
- #
- # return cls(
- # warningPart=kwargs["warningPart"],
- # warningStatus=kwargs["warningStatus"],
- # warningTime=kwargs["warningTime"],
- # warningDesc=kwargs.get("warningDesc", ""),
- # warningUart=kwargs.get("warningUart", "")
- # )
- #
- # def to_dict(self):
- # return {
- # "index": self.warningPart if self.warningPart.isdigit() else 0,
- # "warningStatus": self.warningStatus,
- # "warningTime": self.warningTime,
- # "warningDesc": self.warningDesc
- # }
- #
- # @property
- # def isPart(self):
- # """
- # 是否是部件告警信息
- # :return:
- # """
- # return self.warningPart.isdigit()
- #
- class FrontendLog(Searchable):
- """
- 从前端收集到的日志
- """
- context = StringField(verbose_name = "上下文", default = "")
- cookies = StringField(verbose_name = "cookie", default = "")
- level = StringField(verbose_name = "日志级别")
- content = StringField(verbose_name = "日志内容", min_length = 1)
- path = StringField(verbose_name = "哪个页面")
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '生成时间')
- search_fields = ('content', 'level', 'context', 'path')
- meta = {'collection': 'frontend_logs', 'db_alias': 'logdata'}
- class CardBank(DynamicDocument):
- warnings.warn("CardBank is deprecated", DeprecationWarning)
- CardType = StringField(verbose_name = '银行卡类名', unique = True, null = False)
- CardName = StringField(verbose_name = '银行卡名称', null = False)
- meta = {'collection': 'card_bank', 'db_alias': 'default'}
- buffer = {}
- buffer_lock = ThreadLock()
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @staticmethod
- def acquire_lock():
- CardBank.buffer_lock.acquire_lock()
- @staticmethod
- def release_lock():
- CardBank.buffer_lock.release_lock()
- @staticmethod
- def try_load_and_acquire_lock():
- CardBank.buffer_lock.acquire_lock()
- CardBank.load_in_mem()
- @staticmethod
- def reset_buffer():
- try:
- CardBank.acquire_lock()
- CardBank.buffer = {}
- finally:
- CardBank.release_lock()
- @staticmethod
- def load_in_mem():
- try:
- CardBank.acquire_lock()
- if CardBank.buffer:
- return
- banks = CardBank.objects.all()
- for bank in banks:
- CardBank.buffer[bank.CardType] = bank.CardName
- finally:
- CardBank.release_lock()
- @staticmethod
- def get(cardType):
- try:
- CardBank.try_load_and_acquire_lock()
- if cardType in CardBank.buffer:
- return CardBank.buffer[cardType]
- else:
- return None
- finally:
- CardBank.release_lock()
- @staticmethod
- def has(cardType):
- try:
- CardBank.try_load_and_acquire_lock()
- if cardType in CardBank.buffer:
- return True
- else:
- return False
- finally:
- CardBank.release_lock()
- # : 微信支持的银行列表
- #: REF https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4
- class Banks(DynamicDocument):
- CARD_TYPE_MAP = {
- 'DC': "储蓄卡",
- 'CC': "信用卡",
- 'SCC': "准贷记卡",
- 'PC': "预付费卡"
- }
- bankCode = StringField(verbose_name = u'银行code', unique = True, null = False)
- bankName = StringField(verbose_name = u'银行名称', unique = True, null = False)
- sn = IntField(verbose_name = u'排序序号', default = 0)
- wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
- patterns = ListField(verbose_name = u'匹配正则', default = [])
- meta = {'collection': 'banks', 'db_alias': 'default'}
- buffer = {}
- buffer_lock = ThreadLock()
- def to_dict(self):
- return {
- 'bankCode': self.bankCode,
- 'bankName': self.bankName,
- 'wechatBankCode': self.wechatBankCode,
- 'patterns': self.patterns
- }
- @classmethod
- def get_bank_info(cls, cardNo):
- cls.__load_in_mem()
- bank_info = BankAPI().get_bank_card_info(card_no = cardNo)
- if bank_info:
- bank_code = bank_info['bankCode']
- card_type = bank_info['cardType']
- if bank_info['bankCode'] in cls.buffer['code']:
- bank_name = cls.buffer['code'][bank_code]['bankName']
- bank_name2 = cls.buffer['code'][bank_code]['bankName2']
- card_type = cls.CARD_TYPE_MAP.get(card_type, card_type)
- return {
- 'cardNo': cardNo,
- 'bankCode': bank_code,
- 'cardType': card_type,
- 'bankName': bank_name,
- 'bankName2': bank_name2
- }
- else:
- return {
- 'cardNo': cardNo,
- 'bankCode': bank_code,
- 'cardType': card_type,
- 'bankName': '',
- 'bankName2': ''
- }
- return None
- @classmethod
- def get_public_banks(cls):
- cls.__load_in_mem()
- return cls.buffer['public']
- @classmethod
- def get_personal_banks(cls):
- cls.__load_in_mem()
- return cls.buffer['personal']
- @classmethod
- def support_personal(cls, bankName):
- cls.__load_in_mem()
- return bankName in cls.buffer['personal']
- @classmethod
- def support_public(cls, bankName):
- cls.__load_in_mem()
- return bankName in cls.buffer['public']
- @classmethod
- def get_wechat_bank_code(cls, bankName):
- cls.__load_in_mem()
- if bankName in cls.buffer['personal']:
- return cls.buffer['name'][bankName]['wechatBankCode']
- else:
- return ''
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @classmethod
- def __acquire_lock(cls):
- cls.buffer_lock.acquire_lock()
- @classmethod
- def __release_lock(cls):
- cls.buffer_lock.release_lock()
- def reset_buffer(self):
- try:
- self.__acquire_lock()
- self.buffer = {}
- finally:
- self.__release_lock()
- @classmethod
- def __load_in_mem(cls):
- if cls.buffer:
- return
- try:
- cls.__acquire_lock()
- if cls.buffer:
- return
- cls.buffer = {
- 'public': [], # type: list
- 'personal': [], # type: list
- 'name': {},
- 'code': {}
- }
- banks = cls.objects().order_by('sn')
- for bank in banks:
- cls.buffer['name'][bank.bankName] = bank.to_dict()
- cls.buffer['name'][bank.bankName2] = bank.to_dict()
- cls.buffer['code'][bank.bankCode] = bank.to_dict()
- cls.buffer['public'].append(bank.bankName)
- if bank.bankName2:
- cls.buffer['public'].append(bank.bankName2)
- if bank.wechatBankCode:
- cls.buffer['personal'].append(bank.bankName)
- if bank.bankName2:
- cls.buffer['personal'].append(bank.bankName2)
- finally:
- cls.__release_lock()
- 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没有帮助')
- managerId = StringField(verbose_name = '管理员ID', null = False)
- 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_role(cls, role):
- #: 目前无用户体系,暂时将匿名用户作为终端用户
- role = ROLE.myuser if role == ROLE.anonymoususer else role
- return cls.objects(target__in = ['*', role])
- class Feature(Searchable):
- """
- 特性列表的所有特性,由超级管理员配置
- (角色,特性名称) 唯一
- """
- name = StringField(verbose_name = u'特性名称', min_length = 1)
- key = StringField(verbose_name = u'具体的特性代号,为英文')
- role = StringField(verbose_name = u'特性分配的角色', choices = ROLE.choices())
- # managerId = StringField(verbose_name = u'厂商ID')
- default = BooleanField(verbose_name = u'默认值')
- # by = StringField(verbose_name = u'创建的超级管理员的ID')
- desc = StringField(verbose_name = u'描述')
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
- search_fields = ('key', 'name')
- meta = {
- 'indexes': [
- {'fields': ['name', 'role'], 'unique': True},
- ],
- "collection": "features",
- "db_alias": "default"
- }
- @classmethod
- def for_dealer(cls, **kwargs): return cls.objects(role = ROLE.dealer, **kwargs)
- @classmethod
- def for_agent(cls, **kwargs): return cls.objects(role = ROLE.agent, **kwargs)
- @classmethod
- def for_manager(cls, **kwargs): return cls.objects(role = ROLE.manager, **kwargs)
- def to_dict(self):
- return {
- 'id': str(self.id),
- 'role': self.role,
- 'name': self.name,
- # 'managerId': self.managerId,
- # 'by': self.by,
- 'key': self.key,
- 'default': self.default,
- 'desc': self.desc,
- 'dateTimeAdded': self.dateTimeAdded
- }
- def __repr__(self):
- return '<Feature key=%s, role=%s>' % (self.key, self.role)
- DEFAULT_DEALER_FEATURES = ['show_offline_coins']
- class AddressType(Searchable):
- value = StringField(verbose_name = "地址值", unique = True) #: e.g. school
- label = StringField(verbose_name = "地址名称", default = "") #: e.g. 学校
- showWeight = IntField(verbose_name = '显示权重', default = 0)
- createdAt = DateTimeField(default = datetime.datetime.now)
- meta = {"collection": "AddressType", "db_alias": "default"}
- class UserSearchable(RoleBaseDocument):
- class Status(IterConstant):
- NORMAL = 'normal'
- FORBIDDEN = 'forbidden'
- ABNORMAL = 'abnormal'
- NOWITHDRAW = 'noWithdraw'
- username = StringField(verbose_name = "用户名称:=手机号", max_length = 100)
- password = StringField(verbose_name = "密码", min_length = 1)
- nickname = StringField(verbose_name = '名字,用来标识', default = '')
- remarks = StringField(verbose_name = "备注", default = "")
- avatar = StringField(verbose_name = u"用户头像", default = "")
- activited = BooleanField(verbose_name = "是否激活", default = True)
- status = StringField(verbose_name = "账号状态", default = Status.NORMAL)
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = '添加进来的时间')
- last_login = DateTimeField(default = datetime.datetime.now, verbose_name = '最近登录时间')
- phoneOS = StringField(verbose_name = u"终端操作系统", default = "")
- lastLoginUserAgent = StringField(verbose_name = u"最后一次登录的userAgent", default = "")
- smsVendor = StringField(verbose_name = u'sms提供商', default = '')
- meta = {
- 'abstract': True,
- 'indexes': [
- {
- 'fields': ['$username', '$nickname'],
- 'weights': {'username': 5, 'nickname': 5}
- }
- ]
- }
- def __str__(self):
- return '{}<id={} username={} nickname={}>'.format(
- self.__class__.__name__, str(self.id), self.username, self.nickname)
- @property
- def human_id(self):
- return '{}({})'.format(self.username, self.role)
- @property
- def identity_id(self):
- return '{}_{}'.format(self.role, str(self.id))
- def to_dict(self, shadow = False):
- return {
- 'username': self.username if not shadow else '******',
- 'nickname': self.nickname,
- 'remarks': self.remarks,
- 'activited': self.activited,
- 'status': self.status,
- 'dateTimeAdded': self.dateTimeAdded,
- 'last_login': self.last_login,
- 'role': self.role,
- 'avatar': self.my_avatar
- }
- @classmethod
- def create_user(cls, username, password, **attrs):
- user = cls(username = username, **attrs)
- user.password = make_password(password)
- user.save()
- return user
- def is_authenticated(self):
- return True
- def set_password(self, raw_password):
- self.password = make_password(raw_password)
- self.save()
- return self
- def check_password(self, raw_password):
- return check_password(raw_password, self.password)
- def unlock_login(self):
- lm = LimitAttemptsManager(identifier = self.username, category = "%sLogin" % self.role)
- lm.clear()
- @classmethod
- def get_last_login1(cls, **kwargs):
- return cls.objects(**kwargs).order_by('-last_login').first()
- def __check_feature_setup(self):
- # type:()->(str, set)
- self_features = getattr(self, 'features', None)
- if self_features is None:
- raise ImproperlyConfigured('no features attr provided')
- return self.role, set(self_features)
- ## Feature Module
- @property
- def feature_list(self):
- # type:()->List[dict]
- role, self_features = self.__check_feature_setup()
- features = [_.to_dict() for _ in Feature.objects(role = role)] # type: List[dict]
- rv = [
- {
- 'name': feature['name'],
- 'key': feature['key'],
- 'value': feature['key'] in self_features
- } for feature in features
- ]
- #: unique to user, special features
- # set difference
- unique_features = self_features - {_['key'] for _ in features}
- rv += [{'name': _, 'key': _, 'value': True} for _ in unique_features]
- return rv
- def edit_feature_list(self, feature_list, special = None):
- # type:(list, Optional[str])->int
- self.__check_feature_setup()
- features = [_['key'] for _ in feature_list if _['value']]
- if special:
- features.append(special)
- return self.update(set__features = features)
- @property
- def feature_boolean_map(self):
- # type: ()->Dict[str, bool]
- features = {}
- for _ in self.feature_list:
- if _['key']:
- features.update({_['key']: _['value']})
- return features
- def supports(self, feature):
- # type: (str)->bool
- _, self_features = self.__check_feature_setup()
- return feature in self_features
- @property
- def normal(self):
- return self.status in [self.Status.NORMAL, self.Status.NOWITHDRAW]
- @property
- def forbidden(self):
- return self.status == self.Status.FORBIDDEN
- @property
- def abnormal(self):
- return self.status == self.Status.ABNORMAL
- @property
- def no_withdraw(self):
- return self.status == self.Status.NOWITHDRAW
- @property
- def my_avatar(self):
- if self.avatar:
- return self.avatar
- else:
- return ''
- @property
- def universal_password_login(self):
- return getattr(self, '__universal_password_login__', False)
- @universal_password_login.setter
- def universal_password_login(self, upl):
- setattr(self, '__universal_password_login__', upl)
- class CapitalUser(UserSearchable):
- """
- 带资金以及提现用户
- """
- meta = {
- 'abstract': True,
- }
- INCOME_SOURCE_LIST = list()
- INCOME_SOURCE_TO_TYPE = dict()
- INCOME_TYPE_LIST = list()
- INCOME_TYPE_TO_FIELD = dict()
- DEFAULT_WITHDRAW_OPTIONS = {
- 'autoWithdrawSwitch': False,
- 'autoWithdrawType': "wechat",
- 'autoWithdrawMin': RMB(settings.WITHDRAW_MINIMUM).mongo_amount,
- 'autoWithdrawStrategy': {'type': 'asWeek', 'value': 3},
- 'autoWithdrawBankFee': True,
- 'alipay': {
- 'realName': '',
- 'loginId': ''
- },
- 'wechat': {
- 'realName': ''
- }
- }
- ongoingWithdrawList = ListField(field = StringField())
- inhandWithdrawList = ListField(field = DictField())
- withdrawOptions = DictField(verbose_name = u"提现相关选项", default = DEFAULT_WITHDRAW_OPTIONS)
- bankWithdrawFee = BooleanField(verbose_name = u"银行卡提现手续开关(资金池代理商开关和这个开关为双开关)", default = True)
- @property
- def is_new_join(self):
- one_month_before = datetime.datetime.now() - datetime.timedelta(days = 90)
- if self.dateTimeAdded > one_month_before:
- return True
- else:
- return False
- @classmethod
- def income_field_name(cls, income_type):
- # type: (str)->str
- assert income_type in cls.INCOME_TYPE_LIST, 'not support this income type({})'.format(income_type)
- return cls.INCOME_TYPE_TO_FIELD[income_type]
- @classmethod
- def fund_key(cls, income_type, source_key):
- return '{field}.{key}'.format(field = cls.income_field_name(income_type), key = source_key)
- def balance_dict(self, income_type):
- return dict(getattr(self, self.income_field_name(income_type = income_type)))
- def set_balance(self, income_type, source_key, money):
- # type: (str, str, RMB)->bool
- """
- 单元测试使用.初始化值
- :param income_type:
- :param source_key:
- :param money:
- :return:
- """
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert income_type in self.INCOME_TYPE_LIST, 'not support this source{}'.format(income_type)
- assert source_key, 'source key must not be none'
- query = {'_id': ObjectId(self.id)}
- update = {
- '$set': {
- '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount,
- '{fund_key}.frozenBalance'.format(fund_key = self.fund_key(income_type, source_key)): RMB(
- 0).mongo_amount
- },
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def incr_fund(self, income_type, source_key, money):
- # type: (str, str, RMB)->bool
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
- assert source_key, 'gateway key must not be none'
- query = {'_id': ObjectId(self.id)}
- update = {
- '$inc': {
- '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): money.mongo_amount
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def decr_fund(self, income_type, source_key, money):
- # type: (str, str, RMB)->int
- """
- 正常流程不使用该接口,会让账对不上
- :param income_type:
- :param source_key:
- :param money:
- :return:
- """
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert income_type in self.INCOME_TYPE_LIST, 'not support this source'
- assert source_key, 'gateway key must not be none'
- query = {'_id': ObjectId(self.id)}
- update = {
- '$inc':
- {
- '{fund_key}.balance'.format(fund_key = self.fund_key(income_type, source_key)): (
- -money).mongo_amount
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- @classmethod
- def prepare_sellet_income(cls, object_id, transaction_id, source, source_key, money, selfAgg=False):
- assert isinstance(money, RMB), 'money has to be a RMB instance'
- assert source in cls.INCOME_SOURCE_LIST, 'not support this source'
- assert source_key, 'source key must not be none'
- if money == RMB(0):
- logger.debug('{}<id={}> sellet zero.'.format(cls.__name__, object_id))
- return
- income_type = cls.INCOME_SOURCE_TO_TYPE[source]
- query = {
- '_id': ObjectId(object_id),
- 'ongoingWithdrawList.transaction_id': {
- '$ne': transaction_id
- }
- }
- update = {
- '$inc': {
- '{filed}.{key}.balance'.format(filed=cls.INCOME_TYPE_TO_FIELD[income_type],
- key=source_key): money.mongo_amount
- },
- '$addToSet': {
- 'ongoingWithdrawList': {'transaction_id': transaction_id, 'money': money.mongo_amount}
- }
- }
- if selfAgg:
- update['$inc'].update({
- 'aggregatedIncome.{source}'.format(source=source): money.mongo_amount,
- 'incomeMap.{source}'.format(source=source): money.mongo_amount
- })
- result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
- return bool(result.modified_count == 1)
- @classmethod
- def commit_sellet_income(cls, object_id, transaction_id):
- query = {
- '_id': ObjectId(object_id),
- 'ongoingWithdrawList': {
- '$elemMatch': {
- 'transaction_id': transaction_id
- }
- }
- }
- update = {
- '$pull': {
- 'ongoingWithdrawList': {'transaction_id': transaction_id}
- }
- }
- result = cls.get_collection().update_one(query, update, upsert=False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def sub_balance(self, income_type, source_key = None, only_ledger = True):
- # type: (str, str, bool)->RMB
- balance_dict = self.balance_dict(income_type = income_type) # type: dict
- if not source_key:
- balance_list = []
- for key, value in balance_dict.iteritems():
- if only_ledger:
- if WithdrawGateway.is_ledger(key):
- balance_list.append(value.balance)
- else:
- balance_list.append(value.balance)
- return sum_rmb(balance_list)
- else:
- return balance_dict.get(source_key, Balance()).balance
- @property
- def total_balance(self):
- # type: ()->RMB
- total = RMB(0)
- for income_type in self.INCOME_TYPE_LIST:
- total += self.sub_balance(income_type)
- return total
- def sub_frozen_balance(self, income_type, source_key = None):
- # type: (str, str)->RMB
- field = self.income_field_name(income_type = income_type)
- balance_list = []
- for item in self.inhandWithdrawList:
- if item['field'] != field:
- continue
- if source_key and item['key'] != source_key:
- continue
- balance_list.append(item['value'])
- balance_dict = self.balance_dict(income_type = income_type) # type: dict
- if not source_key:
- for key, value in balance_dict.iteritems():
- if WithdrawGateway.is_ledger(key):
- balance_list.append(value.frozenBalance)
- return sum_rmb(balance_list)
- else:
- balance_list.append(balance_dict.get(source_key, Balance()).frozenBalance)
- return sum_rmb(balance_list)
- @property
- def total_frozen_balance(self):
- # type: ()->RMB
- total = RMB(0)
- for income_type in self.INCOME_TYPE_LIST:
- total += self.sub_frozen_balance(income_type)
- return total
- def re_freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
- """
- 某种情况下, 即使反馈成功, 后续也会退单, 这个时候手工处理, 重新设置经销商inhand列表, 但是不在扣除金额(已经在成功的时候扣除了).
- :param source_key:
- :param income_type:
- :param money:
- :param transaction_id:
- """
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- field = self.income_field_name(income_type = income_type)
- query = {'_id': self.id, 'inhandWithdrawList.transaction_id': {'$ne': transaction_id}}
- update = {
- '$addToSet': {
- 'inhandWithdrawList': {
- 'transaction_id': transaction_id,
- 'field': field,
- 'key': source_key,
- 'value': money.mongo_amount
- }
- }
- }
- result = self.get_collection().update_one(query, update, upsert = False) # type: UpdateResult
- return bool(result.modified_count == 1)
- def _freeze_balance(self, income_type, money, source_key, transaction_id, freeze_type):
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- field = self.income_field_name(income_type = income_type)
- fund_key = self.fund_key(income_type = income_type, source_key = source_key)
- query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): {'$ne': transaction_id}}
- update = {
- '$inc': {
- '{fund_key}.balance'.format(fund_key=fund_key): (-money).mongo_amount
- },
- '$addToSet': {
- freeze_type: {
- 'transaction_id': transaction_id,
- 'field': field,
- 'key': source_key,
- 'value': money.mongo_amount
- }
- }
- }
- result = self.get_collection().update_one(query, update) # type: UpdateResult
- return bool(result.modified_count == 1)
- def _clear_frozen_balance(self, transaction_id, freeze_type):
- query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
- update = {
- '$pull': {
- freeze_type:
- {
- 'transaction_id': transaction_id
- }
- }
- }
- result = self.get_collection().update_one(query, update) # type: UpdateResult
- return bool(result.modified_count == 1)
- def _recover_frozen_balance(self, income_type, money, source_key, transaction_id, freeze_type):
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- fund_key = self.fund_key(income_type=income_type, source_key=source_key)
- query = {'_id': self.id, '{}.transaction_id'.format(freeze_type): transaction_id}
- update = {
- '$inc': {
- '{fund_key}.balance'.format(fund_key=fund_key): money.mongo_amount
- },
- '$pull': {
- freeze_type:
- {
- 'transaction_id': transaction_id
- }
- }
- }
- result = self.get_collection().update_one(query, update) # type: UpdateResult
- return bool(result.modified_count == 1)
- def freeze_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
- """
- 提现的时候冻结经销商的金额
- :param source_key:
- :param income_type:
- :param money:
- :param transaction_id:
- """
- return self._freeze_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
- def clear_frozen_balance(self, transaction_id): # type:(str)->bool
- """
- 提现完成 清理冻结
- :param transaction_id:
- :return:
- """
- assert transaction_id, 'transaction id is null'
- return self._clear_frozen_balance(transaction_id, "inhandWithdrawList")
- def recover_frozen_balance(self, income_type, money, source_key, transaction_id): # type:(str, RMB, str, str)->bool
- """
- 提现遇到问题 直接回滚
- :param source_key:
- :param income_type:
- :param money:
- :param transaction_id:
- :return:
- """
- assert source_key, 'gateway is null'
- assert transaction_id, 'transaction id is null'
- return self._recover_frozen_balance(income_type, money, source_key, transaction_id, "inhandWithdrawList")
- def withdraw_source_key(self, pay_app = None):
- # type:(PayAppBase)->str
- """
- 供PaymentGateway调用获取提现网关
- :param pay_app:
- :return:
- """
- raise NotImplementedError()
- @property
- def withdraw_sms_provider(self):
- # type: () -> WithdrawSMSProvider
- raise NotImplementedError()
- @property
- def withdraw_sms_phone_number(self):
- # type: () -> basestring
- raise NotImplementedError()
- def check_withdraw_min_fee(self, income_type, pay_type, amount):
- raise NotImplementedError()
- def new_withdraw_record(self, withdraw_gateway, pay_entity, source_key, income_type, amount, pay_type, manual,
- recurrent):
- # type: (WithdrawGateway, WithdrawBankCard, str, str, RMB, str, bool, bool) -> WithdrawRecord
- raise NotImplementedError()
- def new_withdraw_handler(self, record):
- # type: (WithdrawRecord) -> WithdrawHandler
- raise NotImplementedError()
- def get_bound_pay_openid(self, key):
- raise NotImplementedError()
- def withdraw_bank_card(self, accountCode):
- # type: (str)->WithdrawBankCard
- return WithdrawBankCard.objects(ownerId = str(self.id), role = self.role, accountCode = accountCode).first()
- @property
- def withdraw_bank_cards(self):
- banks = WithdrawBankCard.objects(ownerId = str(self.id), role = self.role).all()
- return [{
- 'accountCode': bank.accountCode,
- 'bankName': bank.bankName
- } for bank in banks]
- @property
- def auto_withdraw_min(self):
- return self.withdrawOptions.get('autoWithdrawMin')
- @property
- def auto_withdraw_type(self):
- return self.withdrawOptions.get('autoWithdrawType')
- @property
- def auto_withdraw_switch(self):
- return self.withdrawOptions.get('autoWithdrawSwitch')
- @property
- def auto_withdraw_strategy(self):
- return self.withdrawOptions.get('autoWithdrawStrategy', {})
- @property
- def auto_withdraw_bank_fee(self):
- return self.withdrawOptions.get('autoWithdrawBankFee', True)
- @property
- def withdraw_open_id(self):
- return getattr(self, '__withdraw_openid__')
- @withdraw_open_id.setter
- def withdraw_open_id(self, open_id):
- setattr(self, '__withdraw_openid__', open_id)
- @property
- def withdraw_alipay_config(self):
- return self.withdrawOptions.get('alipay', {})
- @property
- def withdraw_alipay_login_id(self):
- return self.withdraw_alipay_config.get('loginId', '')
- @property
- def withdraw_alipay_real_name(self):
- return self.withdraw_alipay_config.get('realName', '')
- @property
- def withdraw_wechat_config(self):
- return self.withdrawOptions.get('wechat', {})
- @property
- def withdraw_wechat_real_name(self):
- return self.withdraw_wechat_config.get('realName', '') or self.nickname
- @property
- def auto_withdraw_bound_open_id(self):
- raise NotImplementedError()
- def can_withdraw_today(self, amount):
- from apps.web.core.exceptions import ServiceException
- count, total = WithdrawRecord.count_today(ownerId = str(self.id), role = self.role)
- if count >= 10:
- raise ServiceException(
- {
- 'result': 0, 'description': u"今日提现次数超限",
- 'payload': {}
- })
- if not self.supports('in_withdraw_whitelist') and self.is_new_join:
- if amount + total > RMB(500):
- raise ServiceException(
- {
- 'result': 0, 'description': u"今日提现金额超限",
- 'payload': {}
- })
- else:
- if total + amount > RMB(settings.WITHDRAW_MAXIMUM):
- raise ServiceException(
- {
- 'result': 0,
- 'description': u"今日提现金额超限",
- 'payload': {}
- })
- @property
- def current_wallet_withdraw_source_key(self):
- logger.debug('get withdraw source key. source = {}'.format(repr(self)))
- from apps.web.helpers import AgentCustomizedBrandVisitor
- return AgentCustomizedBrandVisitor(factory_type = 'withdraw_source_key', pay_app = None).visit(self)
- def withdraw_support(self, source_key):
- from apps.web.agent.models import Agent
- is_ledger, agent, withdraw_gateway_list = Agent.withdraw_gateway_list(source_key)
- if not is_ledger:
- return {
- 'wechat': {
- 'support': False,
- },
- 'alipay': {
- 'support': False,
- },
- 'bank': {
- 'support': False,
- }
- }
- rv = {
- 'wechat': {
- 'support':
- withdraw_gateway_list['wechat'].support_withdraw and not withdraw_gateway_list[
- 'wechat'].manual_withdraw,
- 'realName': self.withdraw_wechat_real_name
- },
- 'alipay': {
- 'support': withdraw_gateway_list['alipay'].support_withdraw and not withdraw_gateway_list[
- 'wechat'].manual_withdraw,
- 'realName': self.withdraw_alipay_real_name,
- 'loginId': self.withdraw_alipay_login_id
- },
- 'bank': {
- 'support': withdraw_gateway_list['wechat'].support_withdraw_bank or withdraw_gateway_list[
- 'alipay'].support_withdraw_bank,
- 'cards': self.withdraw_bank_cards,
- 'transFee': agent.dealerBankWithdrawFee and self.bankWithdrawFee
- }
- }
- return rv
- def set_withdraw_alipay(self, loginId, realName):
- self.update(withdrawOptions__alipay = {'loginId': loginId, 'realName': realName})
- def set_withdraw_wechat(self, realName):
- self.update(withdrawOptions__wechat = {'realName': realName})
- class WithdrawRefundRecord(Searchable):
- withdraw_record_id = ObjectIdField(unique = True)
- createdTime = DateTimeField(default = datetime.datetime.now)
- role = StringField(choices = ROLE.choices())
- meta = {
- "collection": "withdraw_refund_record",
- "db_alias": "logdata"
- }
- class WithdrawRecord(Searchable):
- class WithdrawPayType(IterConstant):
- BANK = 'bank'
- WECHAT = 'wechat'
- order = StringField(verbose_name = u"订单号", unique = True)
- ownerId = StringField(verbose_name = u"提现者对应对象ID")
- role = StringField(verbose_name = u'提现者账号类型', choices = ROLE.choices())
- name = StringField(verbose_name = u"提现人姓名", default = "") # type: str
- phone = StringField(verbose_name = u"持卡人电话", default = "") # type: str
- balance = MonetaryField(verbose_name = u"提现人提现时刻余额", default = RMB('0.00')) # type: RMB
- incomeType = StringField(verbose_name = u'提现收入类型',
- choices = AGENT_INCOME_TYPE.choices() + DEALER_INCOME_TYPE.choices())
- withdrawSourceKey = StringField(verbose_name = u'提现来源KEY', default = None)
- payType = StringField(verbose_name = u"支付方式(微信,银行卡)", default = "") # type: str
- manual = BooleanField(verbose_name = u'是否手动处理', default = False) # type: bool
- recurrent = BooleanField(verbose_name = u"是否为自动循环提现", default = False)
- status = IntField(verbose_name = u"状态", default = WithdrawStatus.SUBMITTED)
- remarks = StringField(verbose_name = u"备注系统内部状态", default = "")
- description = StringField(verbose_name = u'给用户看的描述', default = '')
- postTime = DateTimeField(verbose_name = u"请求时间", default = datetime.datetime.now)
- finishedTime = DateTimeField(verbose_name = u"提现订单完成时间")
- amount = MonetaryField(verbose_name = u"提现金额", default = RMB('0.00')) # type: RMB
- serviceFee = MonetaryField(verbose_name = u"手续费", default = RMB('0.00')) # type: RMB
- actualPay = MonetaryField(verbose_name = u"实际金额", default = RMB('0.00')) # type: RMB
- bankTransFee = MonetaryField(verbose_name = u"转账银行费用", default = RMB('0.00')) # type: RMB
- refunded = BooleanField(verbose_name = u'是否已经退款了', default = False)
- cardUserName = StringField(verbose_name = u"持卡人姓名", default = "")
- accountCode = StringField(verbose_name = u"提现银行卡号", default = "")
- parentBankName = StringField(verbose_name = u"银行名称", default = "")
- subBankName = StringField(verbose_name = u"银行支行名称", default = "")
- payAgentId = StringField(verbose_name = u'提现处理平台代理商ID')
- withdrawFeeRatio = PermillageField(verbose_name = u'提现费率', default = Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO)
- partition = ListField(verbose_name = u'提现费率收入划分', default = [])
- withdrawGatewayKey = StringField(verbose_name = u"提现资金池gateway key", required = True)
- extras = DictField(verbose_name = u"记录提现额外信息", default = {})
- search_fields = ('order', 'phone', 'name')
- meta = {
- 'indexes': [
- {
- 'fields': ['ownerId', 'role'],
- },
- 'status',
- '-postTime',
- 'payAgentId'
- ],
- 'collection': 'WithdrawRecord',
- 'db_alias': 'default'
- }
- @classmethod
- def get_record(cls, order_no):
- return cls.objects(order = order_no).first()
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @classmethod
- def get_succeeded_records(cls, **kwargs):
- return cls.objects(status = WithdrawStatus.SUCCEEDED, **kwargs)
- @classmethod
- def get_processing_via_bank(cls):
- return cls.objects(status__in = [WithdrawStatus.PROCESSING, WithdrawStatus.BANK_PROCESSION],
- payType = WITHDRAW_PAY_TYPE.BANK, manual = False)
- @classmethod
- def get_processing_via_v3(cls):
- return cls.objects(status__in = [WithdrawStatus.PROCESSING], payType = WITHDRAW_PAY_TYPE.WECHAT, manual = False)
- @classmethod
- def get_failed_records(cls):
- return cls.objects(status = WithdrawStatus.FAILED, refunded = False, manual = False)
- @classmethod
- def create(cls, withdraw_user, withdraw_gateway, pay_entity, source_key, income_type, pay_type, fund_map,
- manual, recurrent, **kwargs):
- # type: (CapitalUser, WithdrawGateway, WithdrawBankCard, str, str, str, dict, bool, bool, dict) -> WithdrawRecord
- assert pay_type in (
- WITHDRAW_PAY_TYPE.WECHAT, WITHDRAW_PAY_TYPE.BANK, WITHDRAW_PAY_TYPE.ALIPAY), 'not support this pay type'
- if manual:
- assert pay_type == WITHDRAW_PAY_TYPE.BANK, 'pay type must be bank in manual'
- withdraw_record_payload = {
- 'order': WithdrawRecord.make_no(withdraw_user.role, str(withdraw_user.id)),
- 'ownerId': str(withdraw_user.id),
- 'role': withdraw_user.role,
- 'name': withdraw_user.nickname,
- 'phone': withdraw_user.username,
- 'balance': withdraw_user.sub_balance(income_type = income_type, source_key = source_key),
- 'incomeType': income_type,
- 'withdrawSourceKey': source_key,
- 'payType': pay_type,
- 'manual': manual,
- 'amount': fund_map.get('amount'),
- 'serviceFee': fund_map.get('serviceFee'),
- 'actualPay': fund_map.get('actualPay'),
- 'bankTransFee': fund_map.get('bankTransFee'),
- 'accountCode': pay_entity.accountCode,
- 'cardUserName': pay_entity.accountName,
- 'parentBankName': pay_entity.bankName,
- 'subBankName': pay_entity.branchBankName,
- 'partition': fund_map.get('partition'),
- 'payAgentId': withdraw_gateway.occupantId,
- 'withdrawGatewayKey': withdraw_gateway.gateway_key,
- 'withdrawFeeRatio': fund_map.get('withdrawFeeRatio'),
- 'extras': {'v': 2}
- }
- if pay_type == WITHDRAW_PAY_TYPE.BANK:
- withdraw_record_payload['extras'].update({
- 'withdrawBankCard': pay_entity.id
- })
- if kwargs:
- withdraw_record_payload.update(**kwargs)
- if hasattr(withdraw_gateway, 'version'):
- withdraw_record_payload['extras'].update({
- 'gateway_version': getattr(withdraw_gateway, 'version')
- })
- record = cls(**withdraw_record_payload)
- record.save()
- return record
- @property
- def finished(self):
- return self.status in WithdrawStatus.finished_status()
- def succeed(self, **kwargs):
- payload = {
- 'status': WithdrawStatus.SUCCEEDED
- }
- if kwargs and 'extra' in kwargs:
- extra = kwargs.pop('extra')
- for key, value in extra.iteritems():
- payload.update({
- 'extra.{}'.format(key): value
- })
- if kwargs:
- payload.update(kwargs)
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def fail(self, remarks = '', description = u'支付网关返回失败'):
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': {
- 'status': WithdrawStatus.FAILED,
- 'remarks': remarks,
- 'description': description,
- 'finishedTime': datetime.datetime.now()
- }},
- upsert = False)
- return result.matched_count == 1
- def revoke(self, remarks = '', description = u'提现失败'):
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': {
- 'status': WithdrawStatus.CLOSED,
- 'refunded': True,
- 'remarks': remarks,
- 'description': description,
- 'finishedTime': datetime.datetime.now()
- }},
- upsert = False)
- return result.matched_count == 1
- def set_processing(self, remarks = u'提现申请已经受理', description = u'提现申请已经受理', **kwargs):
- extras = self.extras
- extras.update(**kwargs)
- set_payload = {
- 'status': WithdrawStatus.PROCESSING,
- 'remarks': remarks,
- 'description': description,
- 'extras': extras
- }
- result = self.get_collection().update_one(
- filter = {'_id': ObjectId(self.id),
- 'status': {'$nin': WithdrawStatus.finished_status()}},
- update = {'$set': set_payload},
- upsert = False)
- return result.matched_count == 1
- def set_bank_processing(self, **kwargs):
- extras = self.extras
- extras.update(**kwargs)
- set_payload = {
- 'status': WithdrawStatus.BANK_PROCESSION,
- 'description': u'银行正在处理中',
- 'remarks': '',
- 'extras': extras
- }
- result = self.get_collection().update_one(
- filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': WithdrawStatus.finished_status()
- }
- },
- update = {'$set': set_payload},
- upsert = False)
- return result.matched_count == 1
- @staticmethod
- def make_no(role, user_id):
- # type: (str, str)->str
- assert role in ROLE.choices(), 'no this role'
- return OrderNoMaker.make_order_no_32(identifier = str(user_id),
- main_type = OrderMainType.WITHDRAW,
- sub_type = ROLE.sub_type(role))
- @staticmethod
- def is_my(order_no):
- try:
- if order_no[0] == OrderMainType.WITHDRAW and order_no[1] in ROLE.sub_type_list():
- return True
- elif order_no[14] == OrderMainType.WITHDRAW and order_no[15] in ROLE.sub_type_list():
- return True
- else:
- return False
- except Exception as e:
- return False
- @staticmethod
- def process_memcache_lock(order):
- # type: (str)->None
- return memcache_lock('WithdrawRecord_%s' % order, 'processing')
- @classmethod
- def count_today(cls, ownerId, role = ROLE.dealer):
- count = 0
- total = RMB(0)
- for item in cls.objects(
- ownerId = ownerId, role = role,
- status__nin = [WithdrawStatus.CLOSED, WithdrawStatus.REFUND],
- postTime__gte = get_zero_time(datetime.datetime.now())).all():
- count += 1
- total += item.amount
- return count, total
- class PushRecord(Searchable):
- createdTime = DateTimeField(default = datetime.datetime.now)
- func_name = StringField()
- result = DictField()
- meta = {
- 'collection': 'push_records',
- 'db_alias': 'logdata',
- 'max_documents': 100,
- 'max_size': 2000000
- }
- def __repr__(self): return '<PushRecord func_name=%s>' % (self.func_name,)
- class DealerDoneReportRecord(DynamicDocument):
- reportName = StringField()
- reportDay = StringField()
- dealerIds = ListField()
- expireAt = DateTimeField(default = None)
- meta = {
- 'collection': 'dealer_done_report_record',
- 'db_alias': 'logdata'
- }
- @classmethod
- def update_done_list(cls, report_name, report_day, done_list):
- return cls.objects(reportName = report_name, reportDay = report_day).upsert_one(
- push_all__dealerIds = done_list,
- expireAt = to_datetime(report_day, time_format = "%Y-%m-%d") + datetime.timedelta(days = 7))
- class RefundWithdrawRecord(Searchable):
- """
- 退款提现单,需要记录给经销商/代理商退款的情况
- """
- withdraw_record_id = ObjectIdField(unique = True)
- createdTime = DateTimeField(default = datetime.datetime.now)
- meta = {
- 'abstract': True,
- }
- class ChangeLog(Searchable):
- """
- 在一些敏感的,涉及到需要确保后续可回溯的操作的时候,需要进行记录.
- TODO 这里更加合理的实现,是运用MongoDB的changeStream特性
- 脚本监听,然后过滤处理存储。
- """
- content = StringField()
- event = StringField()
- sender = StringField()
- detail = StrictDictField()
- pre = StrictDictField()
- post = StrictDictField()
- createdTime = DateTimeField(default = datetime.datetime.now)
- meta = {"collection": "change_logs", "db_alias": "logdata"}
- search_fields = ('content', 'event', 'sender')
- def to_dict(self):
- return {
- 'content': self.content,
- 'event': self.event,
- 'sender': self.sender,
- 'detail': self.detail
- }
- class Balance(EmbeddedDocument):
- balance = MonetaryField(verbose_name = '提现收入', default = RMB('0.00'))
- frozenBalance = MonetaryField(verbose_name = '冻结提现收入', default = RMB('0.00'))
- def __repr__(self):
- return "balance = {}, frozenBalance = {}".format(self.balance, self.frozenBalance)
- class AccuracyBalance(EmbeddedDocument):
- balance = AccuracyMoneyField(verbose_name = '提现收入', default = AccuracyRMB('0.00'))
- frozenBalance = AccuracyMoneyField(verbose_name = '冻结提现收入', default = AccuracyRMB('0.00'))
- class TempValues(Searchable):
- """
- 用于存放一些重要信息的临时数据库表,放memcached中不安全,就用这个
- """
- key = StringField(verbose_name = 'key', default = '', unique = True)
- value = StringField(verbose_name = 'value', default = '')
- expireAt = DateTimeField(default = None, verbose_name = u'过期时间')
- meta = {"collection": "temp_values", "db_alias": "logdata"}
- @staticmethod
- def get(key):
- obj = TempValues.objects.filter(__raw__ = {'key': key, '$or': [{'expireAt': {'$exists': False}}, {
- 'expireAt': {'$gte': datetime.datetime.now()}}]}).first()
- if obj:
- return json_loads(obj.value)
- return None
- @staticmethod
- def set(key, value, expire = 60 * 60):
- if isinstance(value, dict):
- _value = json_dumps(value, serialize_type = True)
- else:
- _value = value
- TempValues.objects(key = key).update_one(upsert = True,
- key = key, value = _value, expireAt = datetime.datetime.now() + datetime.timedelta(seconds = expire))
- @staticmethod
- def remove(key):
- try:
- TempValues.objects(key = key).update(
- expireAt = datetime.datetime.now() - datetime.timedelta(seconds = 8 * 60 * 60))
- except Exception, e:
- pass
- class OperatorLog(Searchable):
- """
- 关键用户行为变更日志
- 着重记录用户的并不高频但重要的操作,更改配置项
- """
- class LogLevel(IterConstant):
- INFO = 'INFO'
- NOTICE = 'NOTICE'
- WARNING = 'WARNING'
- CRITICAL = 'CRITICAL'
- EMERGENCY = 'EMERGENCY'
- username = StringField(verbose_name = u'操作员账号')
- operatorId = ObjectIdField(verbose_name=u'操作员关联ID')
- role = StringField(verbose_name = u'操作员角色')
- level = StringField(verbose_name = u'日志级别')
- operatorName = StringField(verbose_name = u'操作名称')
- content = DictField(verbose_name = u'日志内容')
- dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
- meta = {'collection': 'operator_logs', 'db_alias': 'logdata'}
- @classmethod
- def log(cls, user, level, operator_name, content):
- # type: (UserSearchable, basestring, basestring, dict)->None
- try:
- cls(username = user.username,
- role = user.role,
- operatorId = user.id,
- level = level,
- content = content,
- operatorName = operator_name).save()
- except Exception, e:
- logger.exception(e)
- @classmethod
- def log_dev_operation(cls, operator, device, operator_name, content, level=LogLevel.WARNING):
- try:
- content.update({
- 'logicalCode': device.logicalCode,
- 'devNo': device.devNo,
- 'devTypeCode': device.devTypeCode
- })
- return cls(username=operator.username,
- role=operator.role,
- operatorId=operator.id,
- level=level,
- content=content,
- operatorName=operator_name).save()
- except Exception, e:
- logger.exception(e)
- @classmethod
- def record_dev_setting_changes_log(cls, user, operator_name, logicalCode, devTypeCode, content):
- # type: (UserSearchable, basestring, str, str, dict)->Searchable
- try:
- content.update({
- 'logicalCode': logicalCode,
- 'devTypeCode': devTypeCode
- })
- return cls(username = user.username,
- role = user.role,
- operatorId = user.id,
- level = 'INFO',
- content = content,
- operatorName = operator_name).save()
- except Exception, e:
- logger.exception(e)
- class ExceptionLog(Searchable):
- """
- 异常记录
- """
- user = StringField()
- dateTimeAdded = DateTimeField(verbose_name = u'添加时间', default = datetime.datetime.now)
- exception = StringField()
- extra = DictField()
- meta = {'collection': 'exception_log', 'db_alias': 'logdata'}
- @classmethod
- def log(cls, user, exception, extra):
- # type: (str, basestring, dict)->None
- try:
- cls(
- user = user,
- exception = exception,
- extra = extra
- ).save()
- except Exception, e:
- logger.exception(e)
- class MonthlySubTemp(EmbeddedDocument):
- displayName = StringField(verbose_name = u"展示名称", default = "包月套餐")
- price = MonetaryField(verbose_name = u"价格", default = RMB(30))
- numOfMonth = IntField(verbose_name = u"包月的时间 几个月", default = 1)
- def to_dict(self):
- return {
- "displayName": self.displayName,
- "price": str(self.price),
- "numOfMonth": self.numOfMonth
- }
- @classmethod
- def default(cls):
- return cls()
- @classmethod
- def create(cls, **kwargs):
- displayName = kwargs.get("displayName")
- price = kwargs.get("price")
- numOfMonth = kwargs.get("numOfMonth")
- obj = cls.default()
- if displayName is not None:
- obj.displayName = displayName
- if price is not None:
- obj.price = RMB(price)
- if numOfMonth is not None:
- obj.numOfMonth = numOfMonth
- return obj
- # 包月计费模板
- class MonthlyPackageTemp(Searchable):
- """
- 设备类型 和包月计费 拆开 不管什么样的设备类型 次是使用的单位 而每次使用多少 则可以再具体有电量和时间两个基本的计量
- """
- ownerId = StringField(verbose_name = u"包月规则制定的经销商", default = "")
- name = StringField(verbose_name = u"模板名称", default = u"默认模板")
- isDefault = BooleanIntField(verbose_name = u"是否是默认规则", default = 0)
- saleable = BooleanIntField(verbose_name = u"是否开启此模板", default = 0)
- isDelete = BooleanIntField(verbose_name = u"是否被经销商删除", default = 0)
- bothCardAndMobile = BooleanIntField(verbose_name = u"是否通用", default = 0)
- maxCountOfDay = IntField(verbose_name = u"每日的最大的次数", default = 1, min_value = 0)
- maxCountOfMonth = IntField(verbose_name = u"每月的最大次数", default = 30, min_value = 0)
- maxTimeOfCount = IntField(verbose_name = u"每次最大充电量 单位度", default = 240, min_value = 0)
- maxElecOfCount = IntField(verbose_name = u"每次最大的充电时间 单位分钟", default = 1, min_value = 0)
- dateTimeAdded = DateTimeField(verbose_name = u"设置模板的时间", default = datetime.datetime.now)
- dateTimeUpdated = DateTimeField(verbose_name = u"计费模板更新的时间", default = datetime.datetime.now)
- subTemplate = EmbeddedDocumentListField(verbose_name = u"子模版", document_type = MonthlySubTemp,
- default = [MonthlySubTemp.default()])
- meta = {
- "collection": "MonthlyPackageTemp",
- "db_alias": "default"
- }
- __unchargeable_fields = ("id", "ownerId", "dateTimeAdded")
- @classmethod
- def get_package_by_id(cls, _id):
- return cls.objects.filter(id = _id).first()
- @classmethod
- def default_one(cls, ownerId):
- """
- :param ownerId:
- :return:
- """
- obj = cls.objects.filter(ownerId = ownerId, isDefault = 1).first()
- if not obj:
- obj = cls(ownerId = ownerId, isDefault = 1).save()
- return obj
- @classmethod
- def get_template_by_dealer(cls, ownerId):
- """
- 获取经销商的 所有包月模板
- :param ownerId:
- :return:
- """
- objs = cls.objects.filter(ownerId = ownerId, isDelete = 0)
- if not objs:
- return [cls.default_one(ownerId)]
- return objs
- @classmethod
- def get_dealer_package(cls, ownerId, **kwargs):
- return cls.objects.filter(ownerId = ownerId, **kwargs).first()
- def set_default(self):
- """
- 设置为默认
- :return:
- """
- self.__class__.objects.filter(ownerId = self.ownerId, isDefault = 1).update(isDefault = 0)
- self.update(isDefault = 1)
- def to_dict(self):
- result = super(MonthlyPackageTemp, self).to_dict()
- subList = list()
- for _index, _sub in enumerate(self.subTemplate):
- _subResult = _sub.to_dict()
- _subResult["index"] = _index
- subList.append(_subResult)
- result["subTemplate"] = subList
- return result
- def update_by_fields(self, **kwargs):
- """
- 更新 包月套餐的自带属性 某些字段不可更改 直接跳过
- :param kwargs:
- :return:
- """
- fields = self.__class__._fields_ordered
- for _field in fields:
- if kwargs.get(_field) is None:
- continue
- if _field in self.__unchargeable_fields:
- continue
- if _field == "subTemplate":
- # 子模版的保存
- self.subTemplate = [MonthlySubTemp.create(**_sub) for _sub in kwargs.get(_field)]
- continue
- setattr(self, _field, kwargs.get(_field))
- return self.save()
- @classmethod
- def create_by_fields(cls, ownerId, **kwargs):
- obj = cls(ownerId = ownerId)
- return obj.update_by_fields(**kwargs)
- class OrderRecordBase(Searchable):
- meta = {
- 'abstract': True,
- }
- openId = StringField(verbose_name = u"微信ID", default = '')
- nickname = StringField(verbose_name = u'用户昵称', default = '')
- devNo = StringField(verbose_name = u"设备ID", default = None)
- devType = StringField(verbose_name = u"设备类型", default = None)
- devTypeName = StringField(verbose_name = u"设备类型名称", default = None)
- devTypeCode = StringField(verbose_name = u"设备类型编码", default = None)
- logicalCode = StringField(verbose_name = u"设备逻辑编码", default = None)
- groupId = StringField(verbose_name = u"设备地址编号", default = None)
- address = StringField(verbose_name = u"设备地址", default = None)
- groupNumber = StringField(verbose_name = u"设备", default = None)
- groupName = StringField(verbose_name = u"交易场地", default = None)
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'生成时间')
- @property
- def dev_type_name(self):
- if self.devTypeName:
- return self.devTypeName
- if self.devType:
- return self.devType
- return u'未知'
- @property
- def device(self):
- # type:()->DeviceDict
- from apps.web.device.models import Device
- _attr_name = '__device__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- device = Device.get_dev_by_logicalCode(self.logicalCode)
- setattr(self, _attr_name, device)
- return device
- @device.setter
- def device(self, value):
- setattr(self, '__device__', value)
- @property
- def dev_type_code(self):
- if self.devTypeCode:
- return self.devTypeCode
- device = self.device
- if device:
- return device.devTypeCode
- else:
- return ''
- @property
- def group(self):
- # type:()->GroupDict
- from apps.web.device.models import Group
- _attr_name = '__group__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- group = Group.get_group(self.groupId)
- setattr(self, _attr_name, group)
- return group
- @group.setter
- def group(self, group):
- _attr_name = '__group__'
- setattr(self, _attr_name, group)
- @property
- def owner(self):
- from apps.web.dealer.models import Dealer
- _attr_name = '__my_owner__'
- if hasattr(self, _attr_name):
- return getattr(self, _attr_name)
- else:
- ownerId = getattr(self, 'ownerId', None)
- if not ownerId:
- return None
- else:
- dealer = Dealer.objects(id = ownerId).first()
- setattr(self, _attr_name, dealer)
- return dealer
- @property
- def device_identity_info(self):
- return {
- 'logicalCode': self.logicalCode,
- 'devTypeCode': self.devTypeCode,
- 'devTypeName': self.dev_type_name,
- 'groupName': self.groupName,
- 'groupNumber': self.groupNumber,
- 'address': self.address,
- 'groupId': self.groupId
- }
- class WithdrawBanks(BaseDocument):
- CARD_TYPE_MAP = {
- 'DC': "借记卡",
- 'CC': "信用卡",
- 'SCC': "准贷记卡",
- 'PC': "预付费卡"
- }
- code = StringField(verbose_name = u'编号', unique = True, null = False)
- name = StringField(verbose_name = u'名称', null = False)
- bankAbbrevCode = StringField(verbose_name = u'银行编码')
- wechatBankCode = StringField(verbose_name = u'微信银行ID', default = '')
- meta = {'collection': 'withdraw_banks', 'db_alias': 'default'}
- buffer = None
- buffer_lock = ThreadLock()
- def to_dict(self):
- return {
- 'code': self.code,
- 'name': self.name,
- 'wechatBankCode': self.wechatBankCode,
- 'bankAbbrevCode': self.bankAbbrevCode
- }
- @classmethod
- def get_wechat_bank_code(cls, bankName):
- cls.__load_in_mem()
- if bankName in cls.buffer['wechat']:
- return cls.buffer['wechat'][bankName].get('wechatBankCode', '')
- else:
- return ''
- @classmethod
- def get_wechat_support_banks(cls, keyWord):
- cls.__load_in_mem()
- rv = []
- for item in cls.buffer['wechat'].values():
- if keyWord:
- if keyWord in item['name']:
- rv.append({
- 'code': item['code'], 'name': item['name']
- })
- else:
- rv.append({
- 'code': item['code'], 'name': item['name']
- })
- return rv
- @classmethod
- def get_by_bank_abbrev_code(cls, bankAbbrevCode):
- cls.__load_in_mem()
- if bankAbbrevCode in cls.buffer['bankAbbrevCode']:
- bank_info = cls.buffer['bankAbbrevCode'][bankAbbrevCode]
- return {
- 'bankName': bank_info['name']
- }
- else:
- return None
- @classmethod
- def get_all_banks(cls, keyWord):
- cls.__load_in_mem()
- rv = []
- for item in cls.buffer['all'].values():
- if keyWord:
- if keyWord in item['name']:
- rv.append({
- 'code': item['code'], 'name': item['name']
- })
- else:
- rv.append({
- 'code': item['code'], 'name': item['name']
- })
- return rv
- @classmethod
- def support(cls, bankName):
- cls.__load_in_mem()
- return bankName in cls.buffer['all']
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- @classmethod
- def __acquire_lock(cls):
- cls.buffer_lock.acquire_lock()
- @classmethod
- def __release_lock(cls):
- cls.buffer_lock.release_lock()
- def reset_buffer(self):
- try:
- self.__acquire_lock()
- self.buffer = None
- finally:
- self.__release_lock()
- @classmethod
- def __load_in_mem(cls):
- if cls.buffer:
- return
- try:
- cls.__acquire_lock()
- if cls.buffer:
- return
- cls.buffer = {
- 'all': {},
- 'wechat': {},
- 'bankAbbrevCode': {}
- }
- banks = cls.objects().all()
- for bank in banks:
- payload = bank.to_dict()
- cls.buffer['all'][bank.name] = payload
- if bank.wechatBankCode:
- cls.buffer['wechat'][bank.name] = payload
- if bank.bankAbbrevCode:
- cls.buffer['bankAbbrevCode'][bank.bankAbbrevCode] = payload
- finally:
- cls.__release_lock()
- class WithdrawDistrict(BaseDocument):
- code = StringField(verbose_name = u'编号', unique = True, null = False)
- name = StringField(verbose_name = u'名称', null = False)
- parent = StringField(verbose_name = u'父级编号', null = False)
- meta = {
- "collection": "withdraw_district",
- "db_alias": "default"
- }
- @classmethod
- def get_area(cls, province = None):
- parent = '0000' if not province else province
- return [
- {
- 'code': item.code,
- 'name': item.name
- } for item in cls.objects(parent = parent).order_by('-code')
- ]
- class WithdrawBranchBanks(Searchable):
- code = StringField(verbose_name = u'编号', unique = True, null = False)
- name = StringField(verbose_name = u'名称', null = False)
- provinceCode = StringField(verbose_name = u'省编号', null = False)
- cityCode = StringField(verbose_name = u'城市编号', null = False)
- bankCode = StringField(verbose_name = u'银行编号', null = False)
- cnapsCode = StringField(verbose_name = u'联行号')
- search_fields = ('name',)
- meta = {
- "collection": "withdraw_branch_banks",
- "db_alias": "default"
- }
- class WithdrawBankCard(DynamicDocument):
- """
- 提现银行卡信息
- """
- class AccountType(object):
- PERSONAL = 'personal'
- PUBLIC = 'public'
- ownerId = StringField(verbose_name = u"提现者ID", null = False)
- role = StringField(verbose_name = u'提现角色', null = False)
- accountCode = StringField(verbose_name = u"银行卡账号", null = False)
- accountName = StringField(verbose_name = u'持卡人姓名')
- accountType = StringField(verbose_name = u'卡类型(个人账号,对公账号)', default = AccountType.PERSONAL)
- bankCode = StringField(verbose_name = u'银行ID', default = '')
- bankName = StringField(verbose_name = u"银行名称", default = "")
- phone = StringField(verbose_name = u'银行预留电话', default = "")
- # 对公提现需要提供省市支行名称或者联行号
- province = StringField(verbose_name = u"省名称", default = None)
- provinceCode = StringField(verbose_name = u"省编码", default = None)
- city = StringField(verbose_name = u"市名称", default = None)
- cityCode = StringField(verbose_name = u"市编码", default = None)
- branchBankName = StringField(verbose_name = u"银行支行名称", default = None)
- branchBankCode = StringField(verbose_name = u"银行支行编码", default = None)
- cnapsCode = StringField(verbose_name = u"联行号", default = None)
- dateTimeAdded = DateTimeField(default = datetime.datetime.now, verbose_name = u'添加进来的时间')
- manual = BooleanField(verbose_name = u"该卡是否仅手动提现", default = False)
- meta = {"collection": "withdraw_bank_card", "db_alias": "default"}
- @classmethod
- def get_collection(cls):
- return cls._get_collection()
- def to_dict(self):
- if self.accountType == self.AccountType.PERSONAL:
- return {
- 'id': str(self.id),
- 'accountCode': self.accountCode,
- 'accountName': self.accountName,
- 'accountType': self.accountType,
- 'bankName': self.bankName,
- 'bankCode': self.bankCode,
- 'phone': self.phone,
- 'isPublic': False
- }
- else:
- return {
- 'id': str(self.id),
- 'accountCode': self.accountCode,
- 'accountName': self.accountName,
- 'accountType': self.accountType,
- 'bankName': self.bankName,
- 'bankCode': self.bankCode,
- 'phone': self.phone,
- 'province': self.province,
- 'provinceCode': self.provinceCode,
- 'city': self.city,
- 'cityCode': self.cityCode,
- 'branchBankName': self.branchBankName,
- 'branchBankCode': self.branchBankCode,
- 'cnapsCode': self.cnapsCode,
- 'isPublic': True
- }
- @classmethod
- def new_personal_withdraw_bank_card(
- cls, ownerId, role, accountName, accountCode, bankName, phone, id = None):
- if id:
- return cls.objects(id = id).upsert_one(
- ownerId = ownerId, role = role, bankName = bankName,
- accountType = cls.AccountType.PERSONAL, accountName = accountName,
- accountCode = accountCode, phone = phone)
- else:
- return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one(
- ownerId = ownerId, role = role, bankName = bankName,
- accountType = cls.AccountType.PERSONAL, accountName = accountName,
- accountCode = accountCode, phone = phone)
- @classmethod
- def new_public_withdraw_bank_card(cls, ownerId, role, accountName, accountCode, bankCode, bankName,
- provinceCode, province, cityCode, city, branchBankCode, branchBankName,
- cnapsCode, phone, id = None):
- if id:
- return cls.objects(id = id).upsert_one(
- ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName,
- accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode,
- phone = phone, provinceCode = provinceCode, province = province,
- cityCode = cityCode, city = city, branchBankCode = branchBankCode,
- branchBankName = branchBankName, cnapsCode = cnapsCode)
- else:
- return cls.objects(ownerId = ownerId, role = role, accountCode = accountCode).upsert_one(
- ownerId = ownerId, role = role, bankCode = bankCode, bankName = bankName,
- accountType = cls.AccountType.PUBLIC, accountName = accountName, accountCode = accountCode,
- phone = phone, provinceCode = provinceCode, province = province,
- cityCode = cityCode, city = city, branchBankCode = branchBankCode,
- branchBankName = branchBankName, cnapsCode = cnapsCode)
- class RefundOrderBase(Searchable):
- meta = {
- 'abstract': True
- }
- class Status(object):
- """ 退款单的状态 正常流程顺序是从上至下 """
- CREATED = 'created' # 创建
- PROCESSING = 'processing' # 申请中
- FAILURE = 'failure' # 申请失败 (彻底失败 需要重新发起)
- SUCCESS = 'success' # 退款成功 (异步结果)
- CLOSED = 'closed' # 退款失败 (异步结果 不需要重新发起)
- NOORDER = 'noOrder' # 单提交错误
- # 订单常见的状态
- # created ---> processing ---> success (申请 然后接受成功)
- # created ---> failure (直接申请失败)
- # created ---> processing ---> closed (申请成功 回调失败)
- # created ---> processing ---> failure (申请成功 回调失败 不可以重试)
- rechargeObjId = ObjectIdField(verbose_name = u'对应充值单')
- payAppType = StringField(verbose_name = u'支付应用类型', default = None)
- # refundSeq = IntField(verbose_name=u'多次退款,计算退款序列号', default=1)
- orderNo = StringField(verbose_name = u'商户退款订单号', default = '')
- errorCode = StringField(verbose_name = u"错误代码", default = "")
- errorDesc = StringField(verbose_name = u"错误描述", default = "")
- money = MonetaryField(verbose_name = u"退款的金钱数额", default = RMB('0.00'))
- status = StringField(verbose_name = u'订单状态', default = Status.CREATED)
- datetimeAdded = DateTimeField(verbose_name = u"退款创建时间", default = None)
- datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
- finishedTime = DateTimeField(verbose_name = u"退款到账时间")
- extraInfo = DictField(verbose_name = u'额外信息')
- tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = None)
- retryCount = IntField(verbose_name = u"重试次数", default = 0)
- def succeed(self, finishedTime, **kwargs): # type:(datetime.datetime, dict) -> bool
- """
- 退款成功
- :param finishedTime:
- :param kwargs:
- :return:
- """
- payload = {
- 'status': self.Status.SUCCESS,
- 'datetimeUpdated': datetime.datetime.now(),
- 'finishedTime': finishedTime
- }
- if kwargs:
- payload.update(kwargs)
- result = self.get_collection().update_one(
- filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': [self.Status.SUCCESS, self.Status.CLOSED]
- }},
- update = {'$set': payload},
- upsert = False)
- matched = (result.matched_count == 1)
- if matched:
- self.reload()
- return matched
- def fail(self, errorCode = "", errorDesc = "", **kwargs): # type:(str, unicode, dict) -> bool
- """
- 更新为失败状态 表示退款申请失败 这种情况下 可能就需要售后进行介入
- 1. 订单申请发起时候即失败
- 2. 订单申请通过 但是回调的时候明确失败 并且是不可重试的失败
- 3. 此状态即表示订单没退款 理论上可以重新发起退款
- """
- payload = {
- 'status': self.Status.FAILURE,
- 'datetimeUpdated': datetime.datetime.now(),
- 'errorCode': errorCode,
- 'errorDesc': errorDesc
- }
- if kwargs:
- payload.update(kwargs)
- _filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
- }
- }
- result = self.get_collection().update_one(
- filter = _filter,
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def no_order(self, errorCode = "", errorDesc = ""): # type:(basestring, basestring) -> bool
- """
- 查询状态无此订单
- :param errorCode:
- :param errorDesc:
- :return:
- """
- payload = {
- 'status': self.Status.NOORDER,
- 'datetimeUpdated': datetime.datetime.now(),
- 'errorCode': errorCode,
- 'errorDesc': errorDesc
- }
- _filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
- }
- }
- result = self.get_collection().update_one(
- filter = _filter,
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def closed(self, errorCode = "", errorDesc = "", **kwargs): # type:(basestring, basestring, dict) -> bool
- """
- 退款申请成功了 表示合法的退款申请 但是由于某些原因业务进行不下去
- """
- payload = {
- 'status': self.Status.CLOSED,
- 'datetimeUpdated': datetime.datetime.now(),
- 'errorCode': errorCode,
- 'errorDesc': errorDesc
- }
- if kwargs:
- payload.update(kwargs)
- _filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
- }
- }
- result = self.get_collection().update_one(
- filter = _filter,
- update = {'$set': payload},
- upsert = False)
- matched = (result.matched_count == 1)
- if matched:
- self.reload()
- return matched
- def processing(self): # type:() -> bool
- payload = {
- 'status': self.Status.PROCESSING,
- 'datetimeUpdated': datetime.datetime.now()
- }
- _filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$nin': [self.Status.CLOSED, self.Status.SUCCESS]
- }
- }
- result = self.get_collection().update_one(
- filter = _filter,
- update = {'$set': payload},
- upsert = False)
- return result.matched_count == 1
- def retry_processing(self, changeOrderNo): # type:(bool)->bool
- """
- 只有订单处于FAILURE以及NO_ORDER状态的单才会重新调度
- :param changeOrderNo:
- :return:
- """
- RefundOrderHistory.issue(self)
- if changeOrderNo:
- identifier = self.orderNo[16:29]
- orderNo = OrderNoMaker.make_order_no_32(
- identifier = identifier,
- main_type = OrderMainType.REFUND,
- sub_type = RefundSubType.REFUND)
- _payload = {
- 'orderNo': orderNo,
- 'status': self.Status.PROCESSING,
- 'datetimeUpdated': datetime.datetime.now()
- }
- else:
- _payload = {
- 'status': self.Status.PROCESSING,
- 'datetimeUpdated': datetime.datetime.now()
- }
- _filter = {
- '_id': ObjectId(self.id),
- 'status': {
- '$in': [self.Status.FAILURE, self.Status.NOORDER]
- }
- }
- result = self.get_collection().update_one(
- filter = _filter,
- update = {'$set': _payload, '$inc': {'retryCount': int(1)}},
- upsert = False)
- return result.matched_count == 1
- @property
- def is_no_order(self):
- return self.status == self.Status.NOORDER
- @property
- def is_fail(self):
- """ 是否订单失败 """
- return self.status == self.Status.FAILURE
- @property
- def is_closed(self):
- """ 退款单是否已经关闭 目前这个状态没有用 适用于用户手动取消退款申请 """
- return self.status == self.Status.CLOSED
- @property
- def is_success(self):
- """ 退款已经成功 """
- return self.status == self.Status.SUCCESS
- @property
- def is_created(self):
- return self.status == self.Status.CREATED
- @property
- def is_apply(self):
- """ 是否已经发出退款申请 """
- return self.status in [self.Status.CREATED, self.Status.PROCESSING]
- @property
- def is_processing(self):
- return self.status == self.Status.PROCESSING
- @property
- def is_successful(self):
- """ 保留旧的方法 """
- return self.status in [self.Status.SUCCESS, self.Status.PROCESSING]
- @classmethod
- def get_record(cls, **kwargs):
- return cls.objects(**kwargs).first()
- @property
- def refund_income_order(self):
- return None
- @property
- def pay_app_type(self):
- raise AttributeError('must implement pay_app_type.')
- @property
- def pay_sub_order(self):
- # type: ()->Optional[DealerRechargeRecord,RechargeRecord]
- raise AttributeError('must implement pay_sub_order.')
- @pay_sub_order.setter
- def pay_sub_order(self, order):
- raise AttributeError('must implement pay_sub_order setter.')
- @property
- def my_payment_gateway(self):
- if not hasattr(self, '__payment_gateway__'):
- _payment_gateway = PaymentGateway.clone_from_order(self.pay_sub_order)
- setattr(self, '__payment_gateway__', _payment_gateway)
- return getattr(self, '__payment_gateway__')
- @property
- def notify_url(self):
- raise AttributeError('must implement notify_url.')
- class RefundOrderHistory(Searchable):
- """
- 用户退款失败历史记录
- """
- className = StringField(verbose_name = u'对应退款单表名')
- refId = ObjectIdField(verbose_name = u'对应退款单')
- orderNo = StringField(verbose_name = u'原单号', default = '')
- errorCode = StringField(verbose_name = u"错误代码", default = "")
- errorDesc = StringField(verbose_name = u"错误描述", default = "")
- datetimeAdded = DateTimeField(verbose_name = u"创建时间", default = None)
- datetimeUpdated = DateTimeField(verbose_name = u"退款更新时间")
- finishedTime = DateTimeField(verbose_name = u"退款到账时间")
- tradeRefundNo = StringField(verbose_name = u"交易机构退款单号", default = None)
- meta = {
- "collection": "refund_order_history",
- "db_alias": "logdata",
- }
- @classmethod
- def issue(cls, order):
- # type:(RefundOrderBase)->RefundOrderHistory
- return cls(
- className = order.__class__.__name__,
- refId = order.id,
- orderNo = order.orderNo,
- errorCode = order.errorCode,
- errorDesc = order.errorDesc,
- datetimeAdded = datetime.datetime.now(),
- datetimeUpdated = order.datetimeUpdated,
- finishedTime = order.finishedTime,
- tradeRefundNo = order.tradeRefundNo).save()
|