12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604 |
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python
- import datetime
- import hashlib
- import json
- import logging
- import os
- import string
- import time
- import traceback
- import urllib
- import urlparse
- from collections import defaultdict, OrderedDict
- from decimal import Decimal
- from functools import wraps, partial
- import requests
- from aliyunsdkcore.http.method_type import GET
- from django.conf import settings
- from django.core.cache import cache
- from django.utils.module_loading import import_string
- from mongoengine import NotUniqueError, DoesNotExist, MultipleObjectsReturned, Q
- from requests.exceptions import ConnectTimeout
- from typing import TYPE_CHECKING, Optional, Union, Dict
- from apilib.monetary import VirtualCoin, RMB, Ratio
- from apilib.utils import fix_dict
- from apilib.utils_json import JsonResponse
- from apilib.utils_string import cn, get_random_str
- from apilib.utils_url import before_frag_add_query
- from apps import serviceCache
- from apps.web.agent.models import Agent, MoniApp
- from apps.web.common.models import ExceptionLog, MonthlyPackageTemp
- from apps.web.common.proxy import ClientConsumeModelProxy
- from apps.web.common.transaction import OrderNoMaker, OrderMainType, UserPaySubType
- from apps.web.constant import Const, ErrorCode, USER_RECHARGE_TYPE, APP_TYPE, AppPlatformType
- from apps.web.core import PayAppType, ROLE
- from apps.web.core.accounting import Accounting
- from apps.web.core.auth.wechat import WechatAuthBridge
- from apps.web.core.bridge import WechatClientProxy
- from apps.web.core.exceptions import ServiceException, JOSError, InvalidParameter, InterceptException
- from apps.web.core.helpers import ActionDeviceBuilder
- from apps.web.core.models import DlbPayApp, SaobeiPayApp, WechatPayApp, JDAggrePayApp, WechatAuthApp
- from apps.web.dealer.models import Dealer, VirtualCard
- from apps.web.device.define import DeviceChannelType
- from apps.web.device.models import Device, Group
- from apps.web.exceptions import UserServerException
- from apps.web.helpers import get_wechat_auth_bridge, get_wechat_user_manager_mp_proxy, get_app, get_user_manager_agent, \
- DealerCustomizedBrandVisitor, AgentCustomizedBrandVisitor, get_platform_promotion_pay_gateway, get_swap_env_pay_gateway
- from apps.web.report.ledger import Ledger
- from apps.web.user import Cookies, UserAuthState
- from apps.web.user.conf import USER_AUTH_REDIRECT_URL
- from apps.web.user.models import MyUser, Card, RechargeRecord, UserVirtualCard, \
- ServiceProgress, ConsumeRecord, MoniUser, BlackListUsers, Redpack, MyUserDetail
- from apps.web.user.payments import gen_quick_pay_purchase_subject, gen_recharge_purchase_subject
- from apps.web.user.utils2 import ConsumeOrderStateEngine
- from apps.web.utils import supported_app, detect_wechat_client, detect_alipay_client, detect_jdpay_client, \
- detect_jdjr_client, NotSupportedPlatformResponseRedirect, \
- UserRechargeResponseRedirect, UserCenterResponseRedirect, FollowGZHResponseRedirect, \
- CardRechargeResponseRedirect, BtDeviceResponseRedirect, NetDeviceResponseRedirect, HuopoDoorResponseRedirect, \
- ExternalResponseRedirect, CountDownResponseRedirect, \
- MoniGZHResponseRedirect, OneCardGateResponseRedirect, concat_front_end_url
- logger = logging.getLogger(__name__)
- if TYPE_CHECKING:
- from apps.web.device.models import GroupDict, DeviceDict
- from django.http.response import HttpResponse, HttpResponseRedirect
- from apps.web.core.payment import PaymentGateway, WechatMiniPaymentGatewayT
- from django.core.handlers.wsgi import WSGIRequest
- from apps.web.core import PayAppBase
- from apps.web.dealer.models import OnSale
- def get_homepage_response(platform_type, user, dev, chargeIndex, product_agent):
- # type:(str, MyUser, DeviceDict, str, Agent)->HttpResponseRedirect
- def _set_recent_cookies(response):
- # type: (HttpResponse)->None
- response.set_cookie(
- key = Cookies.Recent_DevNo, value = dev.devNo, max_age = 24 * 3600, domain = settings.COOKIE_DOMAIN)
- if platform_type == AppPlatformType.WECHAT:
- moniFollowEnable = dev.owner.supports("moniAPPEnable")
- if moniFollowEnable:
- wechat_mp_proxy = get_wechat_user_manager_mp_proxy(product_agent)
- bound_open_id = user.get_bound_pay_openid(wechat_mp_proxy.bound_openid_key)
- if not bound_open_id:
- logger.error('no bound open id.')
- moniFollowEnable = False
- else:
- moniUser = MoniUser.objects(moniAppId = wechat_mp_proxy.appid, moniOpenId = bound_open_id).first()
- if moniUser:
- if (datetime.datetime.now() - moniUser.checkTime).total_seconds() > 24 * 60 * 60 * 30:
- moniFollowEnable = True
- else:
- moniFollowEnable = False
- if moniFollowEnable:
- # 再查找一次moniApp
- inhouseMoniApp = MoniApp.get_inhouse_app()
- moniAuthProxy = WechatClientProxy(
- WechatAuthApp(appid = inhouseMoniApp.appid, secret = inhouseMoniApp.secret))
- boundOpenId = user.get_bound_pay_openid(moniAuthProxy.bound_openid_key)
- # 找到openId 进行判断 如果不存在boundOpenId 那么需要进行鉴权
- if not boundOpenId:
- moniAuthBridge = WechatAuthBridge(moniAuthProxy.app)
- userState = UserAuthState.by_dev(
- devNo = dev.devNo,
- productAgentId = str(product_agent.id),
- agentId = str(dev.owner.agentId),
- chargeIndex = chargeIndex,
- uid = str(user.id),
- appid = inhouseMoniApp.appid
- )
- return ExternalResponseRedirect(
- moniAuthBridge.generate_auth_url_base_scope(
- redirect_uri = USER_AUTH_REDIRECT_URL.MONI_BASE,
- payload = userState.encode()
- )
- )
- # 存在 boundOpenId 的情况下 去判断下用户到底有没有关注
- moniUser = MoniUser.get_or_create(
- moniOpenId = boundOpenId, moniAppId = moniAuthProxy.appid,
- openId = user.openId, subLogicalCode = dev.logicalCode, subDealerId = dev.ownerId,
- subAgentId = dev.owner.agentId, subPoint = "scabCode"
- )
- if moniUser and moniUser.isSubscribe:
- moniSubscribed = True
- else:
- moniSubscribed = moniAuthProxy.is_subscribe_gzh(boundOpenId)
- # 没有订阅的 需要关注去订阅了 订阅的二维码由appId生成
- if not moniSubscribed:
- logicalCode = dev.logicalCode if dev else ""
- productAgentId = str(product_agent.id)
- openId = user.openId
- chargeIndex = chargeIndex or ""
- # 通过参数生成二维码
- sceneStr = "{}:{}:{}:{}".format(productAgentId, openId, logicalCode, chargeIndex)
- ticket = moniAuthProxy.get_qr_str_scene(sceneStr)
- if ticket:
- return MoniGZHResponseRedirect(ticket)
- # 霍珀首页适配
- if dev["devType"]["code"] == Const.DEVICE_TYPE_CODE_HP_GATE:
- response = HuopoDoorResponseRedirect(l = dev.logicalCode, port = chargeIndex)
- _set_recent_cookies(response)
- return response
- if dev["devType"]["code"] == Const.DEVICE_TYPE_CODE_ONE_CARD:
- response = OneCardGateResponseRedirect(l = dev.logicalCode, port = chargeIndex)
- _set_recent_cookies(response)
- return response
- if dev["devType"]["code"] == Const.DEVICE_TYPE_CODE_AHZT:
- response = CardRechargeResponseRedirect(l = dev.logicalCode)
- _set_recent_cookies(response)
- return response
- # 久恒门禁
- if dev['devType']['code'] == Const.DEVICE_TYPE_CODE_JH_GATE:
- balance = user.calc_currency_balance(dev.owner, dev.group)
- if balance > VirtualCoin(0):
- smart_box = ActionDeviceBuilder.create_action_device(dev)
- try:
- smart_box.start(None, user.openId, None)
- except ServiceException as e:
- logger.error(str(e))
- response = UserCenterResponseRedirect()
- else:
- response = UserRechargeResponseRedirect(dev.logicalCode)
- _set_recent_cookies(response)
- return response
- # 支付宝蓝牙流程
- if dev.channelType == DeviceChannelType.Channel_BT:
- response = BtDeviceResponseRedirect(l = dev.logicalCode, port = chargeIndex)
- _set_recent_cookies(response)
- return response
- # 支持倒计时页面
- smart_box = ActionDeviceBuilder.create_action_device(dev)
- if smart_box.support_count_down(user.openId, chargeIndex) and dev['status'] in [Const.DEV_WORK_STATUS_WORKING,
- Const.DEV_WORK_STATUS_PAUSE]:
- response = CountDownResponseRedirect(devNo = dev.devNo, port = chargeIndex)
- _set_recent_cookies(response)
- return response
- response = NetDeviceResponseRedirect(l = dev.logicalCode, port = chargeIndex)
- _set_recent_cookies(response)
- return response
- def auth_wechat_pay_app(user, dealer, product_agent, state, redirect_uri):
- # type:(MyUser, Dealer, Agent, UserAuthState, str)->Optional[HttpResponseRedirect]
- if dealer.payByMerchant:
- app = get_app(source = dealer,
- app_type = APP_TYPE.WECHAT_ENV_PAY,
- vistor = DealerCustomizedBrandVisitor,
- role = ROLE.myuser)
- else:
- app = get_app(source = product_agent,
- app_type = APP_TYPE.WECHAT_ENV_PAY,
- role = ROLE.myuser) # type: PayAppBase
- # 哆啦宝和扫呗不需要获取OPENID
- if isinstance(app, DlbPayApp) or isinstance(app, SaobeiPayApp):
- logger.debug('auth app<{}> need not to fetch openid.'.format(repr(app)))
- return None
- if isinstance(app, WechatPayApp):
- auth_bridge = WechatAuthBridge(app) # type: WechatAuthBridge
- bound_open_id = user.get_bound_pay_openid(auth_bridge.bound_openid_key)
- if bound_open_id:
- logger.debug('auth bridge<{}> has openid<{}>'.format(repr(auth_bridge), bound_open_id))
- return None
- else:
- logger.debug('auth bridge<{}> need auth to get openid'.format(repr(auth_bridge)))
- return ExternalResponseRedirect(
- auth_bridge.generate_auth_url_base_scope(redirect_uri = redirect_uri,
- payload = state.encode()))
- def auth_wechat_manager_app(my_user, product_agent, state, dev):
- # type:(MyUser, Agent, UserAuthState, DeviceDict)->Optional[HttpResponseRedirect]
- auth_bridge = get_wechat_auth_bridge(product_agent, APP_TYPE.WECHAT_AUTH)
- manager_auth_bridge = get_wechat_auth_bridge(product_agent, APP_TYPE.WECHAT_USER_MANAGER)
- manager_openid = my_user.get_bound_pay_openid(manager_auth_bridge.bound_openid_key)
- if manager_openid and (my_user.managerialOpenId != manager_openid):
- my_user.managerialAppId = manager_auth_bridge.appid
- my_user.managerialOpenId = manager_openid
- my_user.save()
- if manager_openid:
- force_follow_gzh = dev.owner.force_follow_gzh
- if force_follow_gzh:
- wechat_mp_proxy = get_wechat_user_manager_mp_proxy(product_agent)
- if not wechat_mp_proxy.is_subscribe_gzh(manager_openid):
- logger.debug(
- 'user<openId={},groupId={}> need to subscribe gzh.'.format(my_user.openId, my_user.groupId))
- MoniUser.get_or_create(
- moniOpenId = manager_openid, moniAppId = wechat_mp_proxy.appid,
- openId = my_user.openId, subLogicalCode = dev.logicalCode, subDealerId = dev.ownerId,
- subAgentId = dev.owner.agentId, subPoint = "scabCode", checkTime = datetime.datetime.now())
- return FollowGZHResponseRedirect(agentId = str(product_agent.id))
- if manager_openid and my_user.nickname:
- return None
- if auth_bridge.appid == manager_auth_bridge.appid:
- if dev.owner.show_auth_window:
- return ExternalResponseRedirect(
- manager_auth_bridge.generate_auth_url_user_scope(
- redirect_uri = USER_AUTH_REDIRECT_URL.WECHAT_MANAGER_AUTH_USER,
- payload = state.encode()))
- else:
- return None
- else:
- if manager_openid:
- if dev.owner.show_auth_window:
- return ExternalResponseRedirect(
- manager_auth_bridge.generate_auth_url_user_scope(
- redirect_uri = USER_AUTH_REDIRECT_URL.WECHAT_MANAGER_AUTH_USER,
- payload = state.encode()))
- else:
- return None
- else:
- return ExternalResponseRedirect(
- manager_auth_bridge.generate_auth_url_base_scope(
- redirect_uri = USER_AUTH_REDIRECT_URL.WECHAT_MANAGER_AUTH_BASE,
- payload = state.encode()))
- def login_required(func = None, error_response = None):
- if func is None: return partial(login_required, error_response = error_response)
- @wraps(func)
- def wrapper(request, *args, **kwargs):
- if not supported_app(request):
- return NotSupportedPlatformResponseRedirect()
- is_right_role = lambda r: r == request.user.__class__.__name__
- if hasattr(request, 'user') and request.user:
- if (request.user.is_authenticated()) and (any(map(is_right_role, (ROLE.myuser)))):
- return func(request, *args, **kwargs)
- if error_response is None:
- return JsonResponse({"payload": {}}, status = 401)
- else:
- return error_response() if callable(error_response) else error_response
- return wrapper
- def user_balance_cache(openId):
- # type:(str)->str
- return 'balance_{openId}'.format(openId = openId)
- def user_last_time_use_ended_cache(openId, logicalCode):
- # type:(str, str)->str
- return 'last_time_use_{openId}_{logicalCode}'.format(openId = openId, logicalCode = logicalCode)
- def moyaya_half_price_cache(openId, logicalCode):
- # type:(str, str)->str
- return 'half_price_{openId}_{logicalCode}'.format(openId = openId, logicalCode = logicalCode)
- def parse_auth_payload(request):
- # type: (WSGIRequest)->UserAuthState
- state_str = request.GET.get('payload', None)
- if state_str is None:
- raise InvalidParameter(u'所传参数为空')
- state = UserAuthState.decode(state_str)
- if not state.is_valid():
- raise InvalidParameter(u'网络开小差了, 重新扫码试试喔(%s)' % ErrorCode.USER_STATE_IS_NOT_VALID)
- return state
- def calc_real_package(openId, logicalCode, package):
- coins = VirtualCoin(package['coins'])
- money = RMB(package['price'])
- if VirtualCoin(serviceCache.get(moyaya_half_price_cache(openId, logicalCode), -1)) == coins:
- money = money * Decimal('0.50')
- return coins, money
- class OrderBuilderContext(object):
- def __init__(self, user, device, group, pay_gateway):
- self.user = user
- self.device = device
- self.group = group
- self.pay_gateway = pay_gateway
- self.dealer = self.device_or_group.owner
- @property
- def device_or_group(self):
- return self.device or self.group
- class RechargeRecordBuilder(object):
- @staticmethod
- def _new_recharge_record(context, out_trade_no, via, money, coins, subject, attachParas, **kwargs):
- # type: (OrderBuilderContext, str, str, RMB, VirtualCoin, basestring, Dict, Dict)->RechargeRecord
- extra_info = kwargs.pop('extraInfo', {})
- extra_info.update({
- 'dealerId': context.device_or_group.ownerId,
- 'agentId': context.user.agentId
- })
- if money <= RMB(0):
- raise ServiceException({'result': 0, 'description': u'支付金额不得小于或等于0'})
- if context.pay_gateway:
- gateway_type = context.pay_gateway.gateway_type
- assert gateway_type in AppPlatformType.choices(), 'invalid pay gateway type'
- pay_app_type = context.pay_gateway.pay_app_type
- assert pay_app_type in PayAppType.choices(), 'invalid pay app type'
- payload = {
- 'orderNo': out_trade_no,
- 'ownerId': context.device_or_group.ownerId,
- 'money': money,
- 'coins': coins,
- 'wxOrderNo': '',
- 'result': RechargeRecord.PayResult.UNPAY,
- 'via': via,
- 'subject': subject,
- 'attachParas': attachParas,
- 'extraInfo': extra_info
- }
- payload.update(context.device_or_group.identity_info)
- payload.update(**kwargs)
- try:
- return RechargeRecord.issue_pay_record(context=context, **payload)
- except NotUniqueError:
- raise ServiceException({'result': 0, 'description': u'您已发起支付'})
- @staticmethod
- def new_recharge_record(payParam):
- # type: (PayParam)->RechargeRecord
- user = payParam._payer
- device = payParam._device
- group = payParam._group
- ruleId = payParam._ruleId
- attachParas = payParam._attachParas
- pay_gateway = payParam._payGateway
- if not group.rule_is_accepted(ruleId):
- raise ServiceException({'result': 0, 'description': u'组内未找到所提供ruleId'})
- money = RMB(ruleId)
- coins = VirtualCoin(group['ruleDict'][ruleId])
- if coins <= VirtualCoin(0):
- raise ServiceException({'result': 0, 'description': u'充值金币数额不得小于或等于0'})
- logger.info(
- 'make new recharge order. gateway = %s; user = %s; ruleId = %s, money = %s, attachParas = %s'
- % (pay_gateway.gateway_type, repr(user), ruleId, money, attachParas))
- order_no = OrderNoMaker.make_order_no_32(
- identifier = device.logicalCode, main_type = OrderMainType.PAY, sub_type = UserPaySubType.RECHARGE)
- context = OrderBuilderContext(user = user, device = device, group = group, pay_gateway = pay_gateway)
- return RechargeRecordBuilder._new_recharge_record(context=context,
- out_trade_no = order_no,
- via = USER_RECHARGE_TYPE.RECHARGE,
- money = money,
- coins = coins,
- subject = payParam.subject,
- attachParas = attachParas,
- **{
- 'isQuickPay': False,
- 'selectedQuickPayPackageId': None
- })
- @staticmethod
- def new_start_device_record(payParam):
- consumeOrder = payParam._order # type: ConsumeRecord
- pay_gateway = payParam._payGateway
- attachParas = payParam._attachParas
- order_no = OrderNoMaker.make_order_no_32(
- identifier=consumeOrder.logicalCode,
- main_type=OrderMainType.PAY,
- sub_type=UserPaySubType.QUICK_PAY
- )
- context = OrderBuilderContext(
- user=consumeOrder.user,
- device=consumeOrder.device,
- group=consumeOrder.group,
- pay_gateway=pay_gateway
- )
- return RechargeRecordBuilder._new_recharge_record(
- context=context,
- out_trade_no=order_no,
- via=USER_RECHARGE_TYPE.START_DEVICE,
- money=consumeOrder.price,
- coins=consumeOrder.price,
- subject=payParam.subject,
- attachParas=attachParas,
- **{
- 'isQuickPay': True,
- })
- @staticmethod
- def new_consume_order_recharge_record(payParam):
- # type: (PayParam)->RechargeRecord
- consumeRecord = payParam._consumeRecord
- user = payParam._payer
- device = payParam._device
- group = payParam._group
- attachParas = payParam._attachParas
- pay_gateway = payParam._payGateway
- money = RMB(consumeRecord.price)
- coins = VirtualCoin(consumeRecord.price)
- logger.info('make charge consume order. user = %s; money = %s' % (repr(user), money))
- order_no = OrderNoMaker.make_order_no_32(
- identifier=device.logicalCode,
- main_type=OrderMainType.PAY,
- sub_type=UserPaySubType.CASH_PAY
- )
- attachParas.update({"orderNo": consumeRecord.orderNo})
- context = OrderBuilderContext(user=user, device=device, group=group, pay_gateway=pay_gateway)
- return RechargeRecordBuilder._new_recharge_record(
- context=context,
- out_trade_no=order_no,
- via=USER_RECHARGE_TYPE.RECHARGE_CASH,
- money=money,
- coins=coins,
- subject=payParam.subject,
- attachParas=attachParas
- )
- @staticmethod
- def new_card_recharge_record(payParam):
- card = payParam._card
- user = payParam._payer
- device = payParam._device
- group = payParam._group
- attachParas = payParam._attachParas
- pay_gateway = payParam._payGateway
- attachParas.update({
- 'cardNo': card.cardNo
- })
- if card.id: # 直接设备充值的时候构造了一个内存Card, id为空
- attachParas.update({
- 'cardId': str(card.id),
- })
- money = RMB(attachParas.get('price', 0))
- coins = VirtualCoin(attachParas.get('coins'))
- logger.info('make charge card order. user = %s; money = %s' % (repr(user), money))
- order_no = OrderNoMaker.make_order_no_32(identifier=str(attachParas['cardNo']),
- main_type=OrderMainType.PAY,
- sub_type=UserPaySubType.CARD_RECHARGE)
- context = OrderBuilderContext(user=user, device=device, group=group, pay_gateway=pay_gateway)
- return RechargeRecordBuilder._new_recharge_record(context=context,
- out_trade_no=order_no,
- via=USER_RECHARGE_TYPE.RECHARGE_CARD,
- money=money,
- coins=coins,
- subject=payParam.subject,
- attachParas=attachParas)
- class BatteryInfo(object):
- TOKEN_URL = "http://litapi.gmiot.net/1/auth/access_token?"
- TRACK_URL = "http://litapi.gmiot.net/1/devices/tracking?"
- ACCOUNT = "15595278025"
- PW = 'aqkj168'
- TOKEN_FIELD_NAME = "access_token"
- STATUS_FIELD_NAME = "status"
- TOKEN_CACHE_PRE = "AQ_API_TOKEN"
- TOKEN_EXPIRE_TIME = 3600
- @staticmethod
- def myMd5(data):
- return hashlib.md5(data.encode("utf-8")).hexdigest().lower()
- @staticmethod
- def getTokenSign(ntime, pw):
- return BatteryInfo.myMd5(BatteryInfo.myMd5(pw) + ntime)
- @staticmethod
- def sendRequest(url, timeout = 3):
- try:
- result = requests.post(url = url, timeout = timeout)
- if result.status_code != 200:
- raise Exception(u"response status is not equal 200")
- except Exception as e:
- logger.info("api request error, reason is %s" % e)
- raise e
- jsonData = result.json()
- if jsonData.get("ret") != 0:
- logger.error("api request return error ret code ret is %s and msg is %s" % (
- jsonData.get("ret"), jsonData.get("msg")))
- return jsonData
- @staticmethod
- def joinUrl(url, **kwargs):
- from six.moves.urllib import parse
- query_params = parse.urlencode(kwargs)
- return "{}{}".format(url, query_params)
- @staticmethod
- def decodeStatus(status):
- # 第七到八个字节是电池电压信息 单位0.01v
- return int(status[12: 14], 16) + (int(status[14:16], 16) * 0.01)
- @property
- def token(self):
- # 缓存获取token
- token = serviceCache.get(self.TOKEN_CACHE_PRE)
- if not token:
- ntime = str(int(time.time()))
- url = self.joinUrl(self.TOKEN_URL, account = self.ACCOUNT, time = ntime,
- signature = self.getTokenSign(ntime, self.PW))
- data = self.sendRequest(url)
- token = data.get(self.TOKEN_FIELD_NAME)
- serviceCache.set(self.TOKEN_CACHE_PRE, token, 3600)
- return token
- def getBatteryInfo(self, imeis):
- """ 获取电池状态 """
- url = self.joinUrl(self.TRACK_URL, access_token = self.token, imeis = imeis)
- result = dict()
- data = self.sendRequest(url = url).get("data")
- if not data:
- return dict()
- for item in data:
- # 添加电压解析信息
- item.update({"voltage": self.decodeStatus(item.get(self.STATUS_FIELD_NAME))})
- result.update(
- {
- item.get("imei"): item
- }
- )
- return result
- def __call__(self, imeis):
- if isinstance(imeis, list):
- imeis = ",".join(imeis)
- return self.getBatteryInfo(imeis)
- batteryInfo = BatteryInfo()
- class IsNeedRenew(object):
- """
- 虚拟卡是否即将过期
- """
- def __init__(self, userCard = None):
- self._userCard = userCard
- def check_renew(self):
- if not self._userCard or not self._userCard.cardTypeId or self._userCard.continueTime:
- return False, -1
- cardType = self.cardType
- # 有的暂停发售的卡也需要能够续卡
- if cardType is None or (not cardType.renewIgnoreStatus and cardType.status != 1) or (
- cardType.renewIgnoreStatus and cardType.status not in [0, 1]):
- return False, -1
- nowTime = datetime.datetime.now()
- leftDays = (self._userCard.expiredTime - nowTime).days
- if -cardType.needRenewMax < leftDays < cardType.needRenewMin:
- return True, leftDays
- return False, -1
- @property
- def cardType(self):
- """
- 快速查询, 考虑是否需要用缓存实现
- :return:
- """
- # TODO 缓存实现
- cardTypeId = self._userCard.cardTypeId
- _cardType = VirtualCard.objects.filter(id = cardTypeId).first()
- return _cardType
- def __call__(self, userCard):
- self._userCard = userCard
- return self.check_renew()
- is_need_renew = IsNeedRenew()
- def check_user_tel(user):
- return False if user.phone else True
- # 校验用户是否绑定了校园卡
- def check_user_bind_card(user):
- return False if user.extra.get('campus_user_num', None) else True
- def check_black_user(dealerId, openId):
- return BlackListUsers.check_black(openId = openId, dealerId = dealerId)
- def user_pay_coin(openId, devNo, groupId, ownerId, payCount):
- """
- 对用户的金币进行扣款 逐个地址进行扣除
- """
- deduct_list = []
- try:
- Accounting.recordNetPayCoinCount(devNo)
- first_user = MyUser.objects(openId = openId, groupId = groupId).get()
- first_user.update(inc__total_consumed = payCount)
- user_list = [first_user]
- group_list = [str(_.id) for _ in Group.objects(ownerId = ownerId, id__ne = groupId).only('id')]
- last_users = MyUser.objects(openId = openId, balance__gt = 0.0, groupId__in = group_list)
- user_list += list(last_users)
- should_pay = VirtualCoin(payCount)
- for user in user_list: # type: MyUser
- min_pay = min(VirtualCoin(user.balance), VirtualCoin(should_pay))
- deduct_list.append({
- 'id': str(user.id),
- 'field': 'balance',
- 'before': VirtualCoin(user.balance).mongo_amount,
- 'coins': min_pay.mongo_amount
- })
- user.pay(coins=min_pay)
- should_pay -= min_pay
- if should_pay <= VirtualCoin(0):
- break
- # 当前经销商下的所有余额都扣完了,还不够,就要扣代理商下的了(实际也只有代理商支持代理商扣费,才可能到此分支)
- # TODO 这里明显隐含了关系,需要显示出来
- if should_pay > VirtualCoin(0):
- my_users = MyUser.objects.filter(openId = openId, balance__gt = 0.0)
- agentDict = defaultdict()
- for user in my_users:
- if user.is_product_user():
- # TODO 目前自定义公众号进入构成的用户并不记录任何金额信息, 所以直接跳过
- continue
- tempGroup = Group.get_group(user.groupId)
- if tempGroup:
- groupDealer = Dealer.objects(id = tempGroup['ownerId']).get()
- if tempGroup['ownerId'] in agentDict:
- groupAgentId = agentDict[tempGroup['ownerId']]
- if groupDealer.agentId != groupAgentId:
- continue
- else:
- groupAgentId = groupDealer.agentId
- agentDict[tempGroup['ownerId']] = groupDealer.agentId
- min_pay = min(user.balance, should_pay) # type: VirtualCoin
- deduct_list.append({
- 'id': str(user.id),
- 'field': 'balance',
- 'before': VirtualCoin(user.balance).mongo_amount,
- 'coins': min_pay.mongo_amount
- })
- user.pay(coins = min_pay)
- should_pay -= min_pay
- if should_pay <= VirtualCoin(0):
- break
- if should_pay > VirtualCoin(0) or should_pay < VirtualCoin(0):
- raise UserServerException(u'支付扣除金币异常')
- except UserServerException, e:
- logger.error('user<openId={}> has not enough coins to pay for {}. something is wrong.'.format(openId, devNo))
- ExceptionLog.log(
- user = '{}<openId={}>'.format(MyUser.__class__.__name__, openId),
- exception = e.message,
- extra = {'devNo': devNo, 'deduct': deduct_list})
- except Exception as e:
- logger.exception(e)
- ExceptionLog.log(
- user = '{}<openId={}>'.format(MyUser.__class__.__name__, openId),
- exception = traceback.format_exc(),
- extra = {'devNo': devNo, 'deduct': deduct_list})
- def freeze_user_balance(device, group, order, virtual_card = None):
- # type: (DeviceDict, GroupDict, ConsumeRecord, UserVirtualCard)->bool
- if order.paymentInfo['via'] == 'free':
- return True
- elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
- MyUser.freeze_balance(str(order.id), order.paymentInfo['deduct'])
- elif order.paymentInfo['via'] == 'virtualCard':
- if not virtual_card:
- virtual_card = UserVirtualCard.objects(id = str(order.paymentInfo['itemId'])).first() # type: UserVirtualCard
- if virtual_card:
- virtual_card.freeze_quota(order.package, str(order.id))
- else:
- raise ServiceException({'result': 0, 'description': u"不支持的支付类型"})
- def recover_user_frozen_balance(device, group, order, virtual_card = None):
- # type: (DeviceDict, GroupDict, ConsumeRecord, UserVirtualCard)->bool
- if order.paymentInfo['via'] == 'free':
- return True
- elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
- MyUser.recover_frozen_balance(str(order.id), order.paymentInfo['deduct'])
- elif order.paymentInfo['via'] == 'virtualCard':
- if not virtual_card:
- virtual_card = UserVirtualCard.objects(
- id = str(order.paymentInfo['itemId'])).first() # type: UserVirtualCard
- if virtual_card:
- virtual_card.recover_frozen_quota(str(order.id))
- else:
- raise ServiceException({'result': 0, 'description': u"不支持的支付类型"})
- def clear_frozen_user_balance(device, order, usedTime, spendElec, backCoins, user = None, virtual_card = None):
- # type: (DeviceDict, ConsumeRecord, float, float, VirtualCoin, MyUser, UserVirtualCard)->bool
- if order.paymentInfo['via'] == 'free':
- return True
- elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
- if not user:
- user = MyUser.objects(openId = order.openId, groupId = order.groupId).first() # type: MyUser
- if user:
- user.clear_frozen_balance(str(order.id), order.paymentInfo['deduct'], backCoins, order.coin)
- elif order.paymentInfo['via'] == 'virtualCard':
- if not virtual_card:
- virtual_card = UserVirtualCard.objects(
- id = str(order.paymentInfo['itemId'])).first() # type: UserVirtualCard
- if virtual_card:
- virtual_card.clear_frozen_quota(str(order.id), usedTime, spendElec, backCoins)
- else:
- raise ServiceException({'result': 0, 'description': u"不支持的支付类型"})
- def vCard_pay_coin(order, vCard, dev, group, package, attachParas, user = None):
- # type:(ConsumeRecord, UserVirtualCard, DeviceDict, GroupDict, dict, dict, MyUser)->None
- devNo = dev.devNo
- groupId = group['groupId']
- if not user:
- try:
- user = MyUser.objects(openId = order.openId, groupId = groupId).get()
- except DoesNotExist as e:
- logger.error('cannot find user openId=%s, groupId=%s' % (order.openId, groupId))
- raise e
- nickname = user.nickname
- # 扣除卡的额度,并把缓存记录到缓存中
- consumeRcd = vCard.consume(openId = order.openId, group = group, dev = dev, package = package,
- attachParas = attachParas, nickname = nickname)
- if not consumeRcd:
- raise ServiceException({'result': 2, 'description': u"扣除虚拟卡额度的时候,出现异常,请重试"})
- else:
- # TODO 巨大的错误 对于冻结的方式来说 ,itemId 指的是卡Id 对于pay 方式来说 这个id又指的是 消费id 指代不一致 容易导致问题
- # TODO 这个地方重新修改 统一规定为 卡的ID 将代码全部检查一遍 之前定义为rcd id的全部重写逻辑
- order.paymentInfo = {
- 'itemId': str(vCard.id),
- 'via': "virtualCard",
- "money": RMB(order.money).mongo_amount,
- "coins": VirtualCoin(order.coin).mongo_amount,
- "rcdId": str(consumeRcd.id),
- }
- order.save()
- cInfo = Device.get_dev_control_cache(devNo)
- if attachParas.has_key('chargeIndex'):
- port = attachParas.get('chargeIndex')
- if cInfo.has_key(port):
- cInfo[port].update({'consumeRcdId': str(consumeRcd.id)})
- Device.update_dev_control_cache(devNo, cInfo)
- return consumeRcd
- BUILDER_MAP = {
- "RECHARGE": RechargeRecordBuilder.new_recharge_record,
- "START_DEVICE": RechargeRecordBuilder.new_start_device_record,
- "RECHARGE_ORDER": RechargeRecordBuilder.new_consume_order_recharge_record,
- "RECHARGE_CARD": RechargeRecordBuilder.new_card_recharge_record,
- }
- class PayParam(object):
- """
- 用户进入 payGateway 的时候 对于前台入参的解析校验
- 所有的拉起支付,必定需要传入logicalCode或者groupId以及type参数
- """
- def __init__(self, request):
- # type: (WSGIRequest)->None
- self._request = request
- self._clientName = self._get_client_env(request)
- self._ua = request.META.get('HTTP_USER_AGENT')
- self._payer = request.user
- self._payload = self._parse_payload(request)
- self._recordBuilderKey = None
- self._consumeRecord = None
- self._vCard = None
- self._card = None
- self._order = None
- self._payType = self._payload.get("type", None)
- self._attachParas = self._fix_attach_paras(self.payload.get('attachParas', dict()))
- if self._payload.get('logicalCode', None):
- self._device = Device.get_dev_by_l(self._payload.get('logicalCode')) # type: DeviceDict
- self._group = self._device.group # type: GroupDict
- self._dealer = self._device.owner # type: Dealer
- elif self._payload.get('groupId', None):
- self._device = None # type: Optional[DeviceDict]
- self._group = Group.get_group(self._payload.get('groupId')) # type: GroupDict
- self._dealer = self._group.owner # type: Dealer
- elif self._attachParas.get("orderNo"):
- order = get_consume_order(self._attachParas["orderNo"]) # type: ConsumeRecord
- self._device = order.device
- self._group = order.group
- self._dealer = order.group.owner
- else:
- raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1001)'})
- self.common_check()
- self._payGateway = None
- self._subject = None
- self._source = None
- self._money = None
- self._coins = None
- self._discountInfo = None
- self._monthlyPackage = None
- if self._payType == 'startDevice':
- self.handle_param_with_start_device()
- elif self._payType in ['charge', 'forceCharge']:
- self.handle_param_with_recharge()
- elif self._payType == "orderPay":
- self.handle_param_with_order()
- elif self._payType == "chargeCard":
- self.handle_param_with_card()
- else:
- logger.error(
- "error pay type, payload is <{}>, attachParas is <{}>".format(self._payload, self._attachParas)
- )
- raise ServiceException({'result': 0, 'description': u'设备投放地址不属于该平台,请选择其他地址。'})
- product_agent = get_user_manager_agent(self._dealer)
- if self._payer.productAgentId != str(product_agent.id):
- logger.debug('product of {} is {}. is not belong to {}'.format(repr(self._dealer), repr(product_agent), repr(self._payer)))
- raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1002)'})
- self._product_agent = product_agent # type: Agent
- # 获取支付网关
- self.load_pay_gateway()
- @property
- def UA(self):
- return self._ua
- @property
- def clientName(self):
- return self._clientName
- @property
- def _ruleId(self):
- return self._payload.get('ruleId', None) or self._attachParas.get('ruleId', None)
- @staticmethod
- def _fix_attach_paras(attachParas):
- # type: (dict)->dict
- return fix_dict(attachParas, ['chargeIndex'])
- def common_check(self):
- """
- 支付前的公共参数校验 通用的校验都放在这个里面
- 目前校验的
- 1. 是否是拉黑用户 拉黑用户一律不允许充值
- 2. 如果用户是个人中心的充值行为 校验是否是该平台下的用户
- :return:
- """
- # 经销商状态异常
- if not self._dealer.normal:
- raise ServiceException({"result": 0, "description": u"设备经销商账号异常,暂时不能支付。"})
- # 对于用户拉黑的处理
- if check_black_user(dealerId = str(self._dealer.id), openId = self._payer.openId):
- raise ServiceException({"result": 0, "description": u"暂不支持支付,请联系设备经销商解决。"})
- if not self._group:
- raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1002)'})
- # 校验组owner是否一致
- if self._group.ownerId != str(self._dealer.id):
- raise ServiceException({"result": 0, "description": u"设备参数错误,无法支付,请联系设备经销商解决(1001)。"})
- # 校验设备owner是否一致
- if self._device and self._device.ownerId != str(self._dealer.id):
- raise ServiceException({"result": 0, "description": u"设备参数错误,无法支付,请联系设备经销商解决(1002)。"})
- if not self._payType:
- raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1002)'})
- def call_check(self):
- """
- 拉起支付检查. 支付作为唯一检测点
- :return:
- """
- if self._dealer.supports("support_collect_room_info"):
- myUserDetail = MyUserDetail.objects(openId = str(self._payer.openId),
- dealerId = str(self._dealer.id)).first()
- if not (myUserDetail and
- myUserDetail.userName and
- myUserDetail.userPhone and
- myUserDetail.userUnit and
- myUserDetail.userFloor and
- myUserDetail.userRoom):
- raise InterceptException(redirect = before_frag_add_query(
- concat_front_end_url(uri = '/user/index.html#/user/roomVerify'),
- added_query = {
- 'ownerId': str(self._dealer.id),
- 'userName': myUserDetail and myUserDetail.userName or '',
- 'userPhone': myUserDetail and myUserDetail.userPhone or '',
- 'userUnit': myUserDetail and myUserDetail.userUnit or '',
- 'userFloor': myUserDetail and myUserDetail.userFloor or '',
- 'userRoom': myUserDetail and myUserDetail.userRoom or '',
- }))
- @staticmethod
- def _get_client_env(request):
- if detect_wechat_client(request):
- return "wechat"
- elif detect_alipay_client(request):
- return "alipay"
- elif detect_jdpay_client(request) or detect_jdjr_client(request):
- return "jd"
- else:
- raise ServiceException({"result": 0, "description": u"不支持的客户端"})
- def _check_big_package(self, package):
- if RMB(package['price']) > self._dealer.maxPackagePrice:
- raise ServiceException({'result': 0, 'description': u'套餐支付金额超限'})
- def _check_big_charge(self, money):
- agent = Agent.objects.get(id = self._dealer.agentId) # type: Agent
- max_pay_limit = int(agent.maxPayLimit)
- if money > RMB(max_pay_limit):
- raise ServiceException({'result': 0, 'description': u'支付金额超限(%d)' % ErrorCode.TOO_BIG_RECHARGE})
- def handle_param_with_recharge(self):
- """
- 用户直接充值余额的参数处理
- 对于用户的充值
- :return:
- """
- self._recordBuilderKey = "RECHARGE"
- ruleId = self._ruleId
- if not self._group.rule_is_accepted(ruleId):
- raise ServiceException({'result': 0, 'description': u'组内未找到所提供ruleId'})
- money = RMB(ruleId)
- coins = VirtualCoin(self._group['ruleDict'][ruleId])
- self._check_big_charge(money)
- if coins <= VirtualCoin(0):
- raise ServiceException({'result': 0, 'description': u'充值金币数额必须大于0金币'})
- if money <= RMB(0):
- raise ServiceException({'result': 0, 'description': u'支付金额必须大于0元'})
- self._money = money
- self._coins = coins
- self._subject = gen_recharge_purchase_subject(
- major_type=self._device.majorDeviceType[0:8],
- logicalCode=self._device.logicalCode
- )
- if self._payType == 'charge' and 'startKey' in self._attachParas:
- try:
- actionBox = ActionDeviceBuilder.create_action_device(self._device)
- actionBox.check_dev_status(self._attachParas)
- except ServiceException as e:
- logger.info('device(devNo=%s)(via %s)recharge failure. response==%s' % (
- self._device.devNo, self._payGateway.gateway_type, e.result['description'].encode('utf-8'),))
- raise ServiceException({'result': 103, 'description': e.result['description'].encode('utf-8')})
- def handle_param_with_order(self):
- """
- 订单的后支付流程 这个地方其实可以和start_device统一
- :return:
- """
- consumeRecordId = self._attachParas.get("consumeRecordId", "")
- consumeRecord = ConsumeRecord.objects.filter(id=consumeRecordId).first()
- if not consumeRecord:
- raise ServiceException({"result": 0, "description": u"查询消费记录失败,请刷新页面后再尝试支付"})
- if consumeRecord.status != consumeRecord.Status.END:
- raise ServiceException({"result": 0, "description": u"当前订单状态不允许支付"})
- if self._payer.openId != consumeRecord.openId:
- raise ServiceException({'result': 0, 'description': u'不是您消费的订单,无需为此支付费用'})
- if self._group.groupId != consumeRecord.groupId:
- raise ServiceException({'result': 0, 'description': u'支付失败,请联系经销商或客服'})
- self._consumeRecord = consumeRecord
- # 将运行结束的订单 切换为 等待支付的状态
- ConsumeOrderStateEngine(consumeRecord).to_wait_pay()
- self._recordBuilderKey = "RECHARGE_ORDER"
- self._subject = gen_quick_pay_purchase_subject(
- major_type = self._device.majorDeviceType[0:8],
- logicalCode = self._device.logicalCode,
- port = self.selected_port)
- def handle_param_with_start_device(self):
- self._recordBuilderKey = "START_DEVICE"
- orderNo = self.attachParas.get("orderNo")
- order = get_consume_order(orderNo) # type: ConsumeRecord
- if not order:
- raise ServiceException({'result': 0, 'description': u'消费订单查询失败'})
- if order.status != order.Status.WAIT_CONF:
- raise ServiceException({"result": 2, "description": u"订单状态异常,请前往个人中心查看订单"})
- self._order = order
- self._check_big_charge(order.price)
- if self._order.price <= RMB(0):
- raise ServiceException({'result': 0, 'description': u'支付金额必须大于0元'})
- self._subject = gen_recharge_purchase_subject(
- major_type=order.device.majorDeviceType[0:8],
- logicalCode=order.logicalCode
- )
- def handle_param_with_card(self):
- self._recordBuilderKey = "RECHARGE_CARD"
- self._money = RMB(self._attachParas["price"])
- self._coins = VirtualCoin(self._attachParas["coins"])
- cardId = self._attachParas.get("cardId")
- if not cardId:
- raise ServiceException({"result": 0, "description": u"该卡不存在,请联系平台客服处理(1000)"})
- card = Card.objects.filter(id=str(cardId)).first()
- if not card:
- logger.error('card<id={}> not find'.format(cardId))
- raise ServiceException({'result': 0, 'description': u'该卡不存在,请联系平台客服处理(1001)'})
- if not card.dealerId or not card.groupId:
- logger.error('card<id={}> not bind dealer or group'.format(cardId))
- raise ServiceException({
- 'result': ErrorCode.CARD_NEED_BIND_GROUP,
- 'description': u'该卡未绑定开卡地址',
- 'payload': {
- 'cardId': str(card.id),
- 'cardName': card.cardName,
- 'phone': card.phone,
- 'cardNo': card.cardNo
- }})
- if self._group.ownerId != card.dealerId:
- raise ServiceException({'result': 0, 'description': u'该卡不存在,请联系平台客服(1002)'})
- self._card = card
- self._subject = cn(u"实体卡充值 {}".format(card.cardNo))
- def load_pay_gateway(self):
- """
- 加载支付网关 目前不支持商户的模式
- """
- source = self._product_agent
- visitor = AgentCustomizedBrandVisitor
- self._source = source
- funName = "get_{}_env_pay_gateway".format(self._clientName)
- get_env_pay_gateway_func = import_string("apps.web.helpers.{}".format(funName))
- payGateway = get_env_pay_gateway_func(source, role = ROLE.myuser, vistor = visitor)
- self._payGateway = payGateway
- def to_dict(self):
- return {
- "user": self._payer,
- "consumeRecord": self._consumeRecord,
- "vCard": self._vCard,
- "card": self._card,
- "device": self._device,
- "group": self._group,
- "dealer": self._dealer,
- "agent": self._product_agent,
- "gateWay": self._payGateway
- }
- def __str__(self):
- return "pay info is <{}>".format(self.to_dict())
- @property
- def payGateway(self):
- assert self._payGateway.pay_app_type in PayAppType.choices(), 'invalid pay app type'
- return self._payGateway
- @property
- def recordBuilderKey(self):
- assert self._recordBuilderKey in BUILDER_MAP.keys(), 'invalid build key'
- return self._recordBuilderKey
- @property
- def curUser(self):
- return self._payer # type: MyUser
- @property
- def subject(self):
- return self._subject or ""
- @property
- def payload(self):
- return self._payload
- @property
- def attachParas(self):
- return self._attachParas
- @property
- def isQuickPay(self):
- return True if self._payload.get("quickPay") else False
- @property
- def source(self):
- return self._source
- @property
- def goodsInfo(self):
- info = {}
- info.update({
- "goodsName": cn(self._subject),
- "payPrice": str(self._money),
- "goodsPrice": str(self._money),
- "discountInfo": ""
- })
- return info
- @staticmethod
- def _parse_payload(request):
- """
- 解析支付的参数
- 历史遗留原因 之前的支付参数使用的是query传参 参数键为params
- 现在的预下单接口 使用的是body-json传参
- """
- if request.method == GET:
- return json.loads(request.GET.get("params", '{}'))
- else:
- return json.loads(request.body)
- @property
- def showAd(self):
- return self._dealer.ad_show
- @property
- def selected_port(self):
- if not self._attachParas:
- return None
- if 'chargeIndex' in self._attachParas and \
- self._attachParas['chargeIndex'] and \
- self.attachParas['chargeIndex'] not in ['None', 'null', -1, '-1']:
- return str(self._attachParas['chargeIndex'])
- else:
- return None
- class RedpackBuilder(object):
- @staticmethod
- def redpack_to_ledger(order):
- # type:(ConsumeRecord) -> None
- pay_app_type = order.user.gateway
- payGateway = get_platform_promotion_pay_gateway(pay_app_type = pay_app_type)
- # 红包订单建立 并分账
- redpackId = order.attachParas.get('redpackId')
- redpack = Redpack.get_one(redpackId)
- if not redpack:
- logger.error("redpack is null, is not valid record.")
- return
- redpack_attachParas = order.attachParas
- redpack_attachParas.update({'masterId': order.rechargeRcdId, 'startKey': order.startKey})
- dev = Device.get_dev(order.devNo) # type: DeviceDict
- subject = gen_quick_pay_purchase_subject(
- major_type = dev.majorDeviceType[0:8], logicalCode = order.logicalCode, port = order.used_port)
- context = OrderBuilderContext(user = order.user, device = dev, group = dev.group, pay_gateway = payGateway)
- redpack_recharge_record = RechargeRecordBuilder._new_recharge_record(
- context = context,
- out_trade_no = OrderNoMaker.make_order_no_32(
- identifier = order.logicalCode,
- main_type = OrderMainType.PAY,
- sub_type = UserPaySubType.REDPACK
- ),
- via = USER_RECHARGE_TYPE.RECHARGE_REDPACK,
- money = RMB(redpack['redpackMoney']),
- coins = VirtualCoin(redpack['redpackCoins']),
- subject = subject,
- attachParas = redpack_attachParas,
- **{
- 'isQuickPay': True,
- 'selectedQuickPayPackageId': order.attachParas.get('packageId'),
- 'result': RechargeRecord.PayResult.SUCCESS,
- 'finishedTime': datetime.datetime.now(),
- 'description': '平台红包',
- })
- ledger = Ledger(USER_RECHARGE_TYPE.RECHARGE_REDPACK, redpack_recharge_record)
- ledger.execute(journal = False, stats = True, check = False)
- @staticmethod
- def user_pay_coin_with_redpack(order):
- if Redpack.use_redpack(order.attachParas['redpackId'], str(order.id), order.package):
- redpack = Redpack.get_one(order.attachParas['redpackId'])
- deduct_coins = VirtualCoin(redpack.get('redpackCoins', 0))
- pay_count = round(VirtualCoin(order.package['coins']) - deduct_coins, 2)
- if pay_count > 0:
- user_pay_coin(order.openId, order.devNo, order.groupId, order.ownerId, pay_count)
- try:
- RedpackBuilder.redpack_to_ledger(order)
- except:
- import traceback
- logger.error(traceback.format_exc())
- @staticmethod
- def bt_user_pay_coin_with_redpack(order):
- if Redpack.use_redpack(order.attachParas['redpackId'], str(order.id), order.package):
- redpack = Redpack.get_one(order.attachParas['redpackId'])
- deduct_coins = VirtualCoin(redpack.get('redpackCoins', 0))
- pay_count = VirtualCoin(order.package['coins']) - deduct_coins
- if pay_count > VirtualCoin(0):
- device = Device.get_dev_by_l(order.logicalCode)
- errCode, errMsg = order.user.deduct_fee_for_bt(device, pay_count)
- if errCode != 1:
- raise ServiceException({'result': errCode, 'description': errMsg})
- try:
- RedpackBuilder.redpack_to_ledger(order)
- except:
- import traceback
- logger.error(traceback.format_exc())
- @staticmethod
- def get_alipay_cpa_by_ruhui(openId, logicalCode, urlId='', showType='', **kw):
- if not urlId:
- urlId = ConsumeRecord.make_no()
- kw.update({
- 'delayPay': 'true',
- 'urlId': urlId,
- 'logicalCode': logicalCode
- })
- dev = Device.get_dev_by_l(logicalCode)
- from apps.thirdparties.aliyun import AliRuHui
- RH = AliRuHui()
- extra = RedpackBuilder._connection_string(kw)
- params = {
- 'urlId': urlId,
- # 'channelId': settings.ALIPAY_RUHUI_CHANNEL_ID,
- 'alipayOpenId': openId,
- 'outerCode': logicalCode,
- 'extra': extra,
- 'optionType': '1'
- }
- popUpQueryRequest = RH.PopUpQueryRequest(params) # Type: Dict
- if popUpQueryRequest.get('Status') == True:
- money = RedpackBuilder.calc_by_amount(RMB(popUpQueryRequest.get('UnionAmount', 0)))
- if money == RMB(0):
- return {}
- else:
- import urlparse
- taskId = dict(urlparse.parse_qsl(popUpQueryRequest.get('Url'))).get('taskId')
- redpack = Redpack.create_redpack_by_ruhui(
- taskId=taskId,
- openId=openId,
- urlId=urlId,
- money=money,
- leastPayMoney=RMB(0),
- gateway='alipay',
- logicalCode=logicalCode,
- devNo=dev.devNo,
- extra={'ApiPopUpQueryRequest': popUpQueryRequest},
- showType=showType)
- return {'url': popUpQueryRequest.get('Url'), 'money': redpack.money.mongo_amount,
- 'redpackId': str(redpack.id), 'showType': showType, }
- else:
- return {}
- @staticmethod
- def get_alipay_cpa_by_ruhui_v3(openId, logicalCode, showType='', **kw):
- from apps.thirdparties.aliyun import AlipayYunMaV3
- RH = AlipayYunMaV3()
- dev = Device.get_dev_by_logicalCode(logicalCode)
- result = RH.get_cpa_ruhui_body(openId, logicalCode)
- logger.info(result)
- if result.body.success == True and len(result.body.result.seatbid[0].bid[0].ads):
- seatb = result.body.result.seatbid[0]
- urlId = result.body.result.id
- money = RMB.fen_to_yuan(seatb.bid[0].ads[0].price)
- money = RedpackBuilder.calc_by_amount(money)
- if money == RMB(0):
- return {}
- else:
- import urlparse
- url = seatb.bid[0].ads[0].crurl
- taskId = seatb.bid[0].ads[0].id
- redpack = Redpack.create_redpack_by_ruhui(
- taskId=taskId,
- openId=openId,
- urlId=urlId,
- money=money,
- leastPayMoney=RMB(0),
- gateway='alipay',
- logicalCode=logicalCode,
- devNo=dev.devNo,
- extra={'ApiRequest': json.dumps(result.body.result.to_map(), ensure_ascii=False)},
- showType=showType)
- return {'url': url, 'money': redpack.money.mongo_amount,
- 'redpackId': str(redpack.id), 'showType': showType, 'urlId': urlId}
- else:
- return {}
- @staticmethod
- def _connection_string(kw):
- # type: (dict) -> unicode
- """
- 拼接字符串
- :param kw:
- :return:
- """
- return "&".join("{}={}".format(*_) for _ in kw.items())
- @classmethod
- def calc_by_amount(cls, unionAmount):
- # type: (RMB) -> Optional[RMB, bool]
- """
- 随机金额
- """
- random_list = [0.1, 0.2, 0.2, 0.3, 0.3, 0.3, 0.4, 0.4, 0.5]
- import random
- radio = Ratio(random.choice(random_list))
- return unionAmount * radio
- @classmethod
- def get_alipay_cpa_by_laxin(cls, openId, channelId=None):
- TASKMAPNAME={
- '1': '关注天猫店铺领红包',
- '2': '关注天猫店铺领红包',
- '3': '关注淘宝店铺领红包',
- '4': '关注淘宝店铺领红包',
- '5': '浏览商品任务领红包',
- '6': '浏览商品任务领红包',
- '7': '看直播领红包',
- '8': '看直播领红包',
- }
- from apps.thirdparties.aliyun import AliLaXin
- ali = AliLaXin()
- resp = ali.QueryUnionPromotionRequest({'alipayOpenId': openId, 'channelId': channelId})
- code = resp.get('ErrorCode')
- dataList = []
- if code == 0:
- dataList = resp.get('Result', [])
- for item in dataList:
- item['alipayOpenId'] = openId
- item['channel'] = channelId or settings.ALIPAY_LAXIN_CHANNEL_ID
- item['taskName'] = TASKMAPNAME.get(item.get('taskType', ''), '')
- return dataList
- @classmethod
- def query_cpa_laxin_task_status(cls, taskId, openId):
- from apps.thirdparties.aliyun import AliLaXin
- ali = AliLaXin()
- resp = ali.GetUnionTaskStatusRequest({'taskId': taskId, 'alipayOpenId': openId})
- code = resp.get('ErrorCode')
- result = resp.get('Result')
- success = resp.get('Success')
- if code == 0 and result == True and success == True:
- return True
- else:
- return False
- @classmethod
- def create_cpa_laxin_redpack(cls, dev, taskId, openId, taskType):
- effectTime = datetime.datetime.now()
- expiredTime = datetime.datetime.now() + datetime.timedelta(days=30 * 3)
- return Redpack.create_redpack_by_laxin(factoryCode=taskId, openId=openId,
- money=RMB(0.01), leastPayMoney=RMB(0),
- effectTime=effectTime, expiredTime=expiredTime, gateway='alipay',
- logicalCode=dev.logicalCode, devNo=dev.devNo, extra={'taskType':taskType})
- @staticmethod
- def _set_alipay_key(openId, taskId, urlId, money, showType):
- cache.set('alipayAd{}'.format(openId), {'taskId': taskId, 'urlId': urlId, 'money': money, 'showType': showType})
- @staticmethod
- def _pop_alipay_key(openId):
- result = cache.get('alipayAd{}'.format(openId))
- if result:
- cache.delete('alipayAd{}'.format(openId))
- return result
- def get_consume_order(orderNo): # type: (str) -> Optional[ConsumeRecord, None]
- return ConsumeRecord.objects.filter(Q(orderNo=orderNo) | Q(sequenceNo=orderNo)).first()
|