utils.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import datetime
  4. import hashlib
  5. import json
  6. import logging
  7. import os
  8. import string
  9. import time
  10. import traceback
  11. import urllib
  12. import urlparse
  13. from collections import defaultdict, OrderedDict
  14. from decimal import Decimal
  15. from functools import wraps, partial
  16. import requests
  17. from aliyunsdkcore.http.method_type import GET
  18. from django.conf import settings
  19. from django.core.cache import cache
  20. from django.utils.module_loading import import_string
  21. from mongoengine import NotUniqueError, DoesNotExist, MultipleObjectsReturned, Q
  22. from requests.exceptions import ConnectTimeout
  23. from typing import TYPE_CHECKING, Optional, Union, Dict
  24. from apilib.monetary import VirtualCoin, RMB, Ratio
  25. from apilib.utils import fix_dict
  26. from apilib.utils_json import JsonResponse
  27. from apilib.utils_string import cn, get_random_str
  28. from apilib.utils_url import before_frag_add_query
  29. from apps import serviceCache
  30. from apps.web.agent.models import Agent, MoniApp
  31. from apps.web.common.models import ExceptionLog, MonthlyPackageTemp
  32. from apps.web.common.proxy import ClientConsumeModelProxy
  33. from apps.web.common.transaction import OrderNoMaker, OrderMainType, UserPaySubType
  34. from apps.web.constant import Const, ErrorCode, USER_RECHARGE_TYPE, APP_TYPE, AppPlatformType
  35. from apps.web.core import PayAppType, ROLE
  36. from apps.web.core.accounting import Accounting
  37. from apps.web.core.auth.wechat import WechatAuthBridge
  38. from apps.web.core.bridge import WechatClientProxy
  39. from apps.web.core.exceptions import ServiceException, JOSError, InvalidParameter, InterceptException
  40. from apps.web.core.helpers import ActionDeviceBuilder
  41. from apps.web.core.models import DlbPayApp, SaobeiPayApp, WechatPayApp, JDAggrePayApp, WechatAuthApp
  42. from apps.web.dealer.models import Dealer, VirtualCard
  43. from apps.web.device.define import DeviceChannelType
  44. from apps.web.device.models import Device, Group
  45. from apps.web.exceptions import UserServerException
  46. from apps.web.helpers import get_wechat_auth_bridge, get_wechat_user_manager_mp_proxy, get_app, get_user_manager_agent, \
  47. DealerCustomizedBrandVisitor, AgentCustomizedBrandVisitor, get_platform_promotion_pay_gateway, get_swap_env_pay_gateway
  48. from apps.web.report.ledger import Ledger
  49. from apps.web.user import Cookies, UserAuthState
  50. from apps.web.user.conf import USER_AUTH_REDIRECT_URL
  51. from apps.web.user.models import MyUser, Card, RechargeRecord, UserVirtualCard, \
  52. ServiceProgress, ConsumeRecord, MoniUser, BlackListUsers, Redpack, MyUserDetail
  53. from apps.web.user.payments import gen_quick_pay_purchase_subject, gen_recharge_purchase_subject
  54. from apps.web.user.utils2 import ConsumeOrderStateEngine
  55. from apps.web.utils import supported_app, detect_wechat_client, detect_alipay_client, detect_jdpay_client, \
  56. detect_jdjr_client, NotSupportedPlatformResponseRedirect, \
  57. UserRechargeResponseRedirect, UserCenterResponseRedirect, FollowGZHResponseRedirect, \
  58. CardRechargeResponseRedirect, BtDeviceResponseRedirect, NetDeviceResponseRedirect, HuopoDoorResponseRedirect, \
  59. ExternalResponseRedirect, CountDownResponseRedirect, \
  60. MoniGZHResponseRedirect, OneCardGateResponseRedirect, concat_front_end_url
  61. logger = logging.getLogger(__name__)
  62. if TYPE_CHECKING:
  63. from apps.web.device.models import GroupDict, DeviceDict
  64. from django.http.response import HttpResponse, HttpResponseRedirect
  65. from apps.web.core.payment import PaymentGateway, WechatMiniPaymentGatewayT
  66. from django.core.handlers.wsgi import WSGIRequest
  67. from apps.web.core import PayAppBase
  68. from apps.web.dealer.models import OnSale
  69. def get_homepage_response(platform_type, user, dev, chargeIndex, product_agent):
  70. # type:(str, MyUser, DeviceDict, str, Agent)->HttpResponseRedirect
  71. def _set_recent_cookies(response):
  72. # type: (HttpResponse)->None
  73. response.set_cookie(
  74. key = Cookies.Recent_DevNo, value = dev.devNo, max_age = 24 * 3600, domain = settings.COOKIE_DOMAIN)
  75. if platform_type == AppPlatformType.WECHAT:
  76. moniFollowEnable = dev.owner.supports("moniAPPEnable")
  77. if moniFollowEnable:
  78. wechat_mp_proxy = get_wechat_user_manager_mp_proxy(product_agent)
  79. bound_open_id = user.get_bound_pay_openid(wechat_mp_proxy.bound_openid_key)
  80. if not bound_open_id:
  81. logger.error('no bound open id.')
  82. moniFollowEnable = False
  83. else:
  84. moniUser = MoniUser.objects(moniAppId = wechat_mp_proxy.appid, moniOpenId = bound_open_id).first()
  85. if moniUser:
  86. if (datetime.datetime.now() - moniUser.checkTime).total_seconds() > 24 * 60 * 60 * 30:
  87. moniFollowEnable = True
  88. else:
  89. moniFollowEnable = False
  90. if moniFollowEnable:
  91. # 再查找一次moniApp
  92. inhouseMoniApp = MoniApp.get_inhouse_app()
  93. moniAuthProxy = WechatClientProxy(
  94. WechatAuthApp(appid = inhouseMoniApp.appid, secret = inhouseMoniApp.secret))
  95. boundOpenId = user.get_bound_pay_openid(moniAuthProxy.bound_openid_key)
  96. # 找到openId 进行判断 如果不存在boundOpenId 那么需要进行鉴权
  97. if not boundOpenId:
  98. moniAuthBridge = WechatAuthBridge(moniAuthProxy.app)
  99. userState = UserAuthState.by_dev(
  100. devNo = dev.devNo,
  101. productAgentId = str(product_agent.id),
  102. agentId = str(dev.owner.agentId),
  103. chargeIndex = chargeIndex,
  104. uid = str(user.id),
  105. appid = inhouseMoniApp.appid
  106. )
  107. return ExternalResponseRedirect(
  108. moniAuthBridge.generate_auth_url_base_scope(
  109. redirect_uri = USER_AUTH_REDIRECT_URL.MONI_BASE,
  110. payload = userState.encode()
  111. )
  112. )
  113. # 存在 boundOpenId 的情况下 去判断下用户到底有没有关注
  114. moniUser = MoniUser.get_or_create(
  115. moniOpenId = boundOpenId, moniAppId = moniAuthProxy.appid,
  116. openId = user.openId, subLogicalCode = dev.logicalCode, subDealerId = dev.ownerId,
  117. subAgentId = dev.owner.agentId, subPoint = "scabCode"
  118. )
  119. if moniUser and moniUser.isSubscribe:
  120. moniSubscribed = True
  121. else:
  122. moniSubscribed = moniAuthProxy.is_subscribe_gzh(boundOpenId)
  123. # 没有订阅的 需要关注去订阅了 订阅的二维码由appId生成
  124. if not moniSubscribed:
  125. logicalCode = dev.logicalCode if dev else ""
  126. productAgentId = str(product_agent.id)
  127. openId = user.openId
  128. chargeIndex = chargeIndex or ""
  129. # 通过参数生成二维码
  130. sceneStr = "{}:{}:{}:{}".format(productAgentId, openId, logicalCode, chargeIndex)
  131. ticket = moniAuthProxy.get_qr_str_scene(sceneStr)
  132. if ticket:
  133. return MoniGZHResponseRedirect(ticket)
  134. # 霍珀首页适配
  135. if dev["devType"]["code"] == Const.DEVICE_TYPE_CODE_HP_GATE:
  136. response = HuopoDoorResponseRedirect(l = dev.logicalCode, port = chargeIndex)
  137. _set_recent_cookies(response)
  138. return response
  139. if dev["devType"]["code"] == Const.DEVICE_TYPE_CODE_ONE_CARD:
  140. response = OneCardGateResponseRedirect(l = dev.logicalCode, port = chargeIndex)
  141. _set_recent_cookies(response)
  142. return response
  143. if dev["devType"]["code"] == Const.DEVICE_TYPE_CODE_AHZT:
  144. response = CardRechargeResponseRedirect(l = dev.logicalCode)
  145. _set_recent_cookies(response)
  146. return response
  147. # 久恒门禁
  148. if dev['devType']['code'] == Const.DEVICE_TYPE_CODE_JH_GATE:
  149. balance = user.calc_currency_balance(dev.owner, dev.group)
  150. if balance > VirtualCoin(0):
  151. smart_box = ActionDeviceBuilder.create_action_device(dev)
  152. try:
  153. smart_box.start(None, user.openId, None)
  154. except ServiceException as e:
  155. logger.error(str(e))
  156. response = UserCenterResponseRedirect()
  157. else:
  158. response = UserRechargeResponseRedirect(dev.logicalCode)
  159. _set_recent_cookies(response)
  160. return response
  161. # 支付宝蓝牙流程
  162. if dev.channelType == DeviceChannelType.Channel_BT:
  163. response = BtDeviceResponseRedirect(l = dev.logicalCode, port = chargeIndex)
  164. _set_recent_cookies(response)
  165. return response
  166. # 支持倒计时页面
  167. smart_box = ActionDeviceBuilder.create_action_device(dev)
  168. if smart_box.support_count_down(user.openId, chargeIndex) and dev['status'] in [Const.DEV_WORK_STATUS_WORKING,
  169. Const.DEV_WORK_STATUS_PAUSE]:
  170. response = CountDownResponseRedirect(devNo = dev.devNo, port = chargeIndex)
  171. _set_recent_cookies(response)
  172. return response
  173. response = NetDeviceResponseRedirect(l = dev.logicalCode, port = chargeIndex)
  174. _set_recent_cookies(response)
  175. return response
  176. def auth_wechat_pay_app(user, dealer, product_agent, state, redirect_uri):
  177. # type:(MyUser, Dealer, Agent, UserAuthState, str)->Optional[HttpResponseRedirect]
  178. if dealer.payByMerchant:
  179. app = get_app(source = dealer,
  180. app_type = APP_TYPE.WECHAT_ENV_PAY,
  181. vistor = DealerCustomizedBrandVisitor,
  182. role = ROLE.myuser)
  183. else:
  184. app = get_app(source = product_agent,
  185. app_type = APP_TYPE.WECHAT_ENV_PAY,
  186. role = ROLE.myuser) # type: PayAppBase
  187. # 哆啦宝和扫呗不需要获取OPENID
  188. if isinstance(app, DlbPayApp) or isinstance(app, SaobeiPayApp):
  189. logger.debug('auth app<{}> need not to fetch openid.'.format(repr(app)))
  190. return None
  191. if isinstance(app, WechatPayApp):
  192. auth_bridge = WechatAuthBridge(app) # type: WechatAuthBridge
  193. bound_open_id = user.get_bound_pay_openid(auth_bridge.bound_openid_key)
  194. if bound_open_id:
  195. logger.debug('auth bridge<{}> has openid<{}>'.format(repr(auth_bridge), bound_open_id))
  196. return None
  197. else:
  198. logger.debug('auth bridge<{}> need auth to get openid'.format(repr(auth_bridge)))
  199. return ExternalResponseRedirect(
  200. auth_bridge.generate_auth_url_base_scope(redirect_uri = redirect_uri,
  201. payload = state.encode()))
  202. def auth_wechat_manager_app(my_user, product_agent, state, dev):
  203. # type:(MyUser, Agent, UserAuthState, DeviceDict)->Optional[HttpResponseRedirect]
  204. auth_bridge = get_wechat_auth_bridge(product_agent, APP_TYPE.WECHAT_AUTH)
  205. manager_auth_bridge = get_wechat_auth_bridge(product_agent, APP_TYPE.WECHAT_USER_MANAGER)
  206. manager_openid = my_user.get_bound_pay_openid(manager_auth_bridge.bound_openid_key)
  207. if manager_openid and (my_user.managerialOpenId != manager_openid):
  208. my_user.managerialAppId = manager_auth_bridge.appid
  209. my_user.managerialOpenId = manager_openid
  210. my_user.save()
  211. if manager_openid:
  212. force_follow_gzh = dev.owner.force_follow_gzh
  213. if force_follow_gzh:
  214. wechat_mp_proxy = get_wechat_user_manager_mp_proxy(product_agent)
  215. if not wechat_mp_proxy.is_subscribe_gzh(manager_openid):
  216. logger.debug(
  217. 'user<openId={},groupId={}> need to subscribe gzh.'.format(my_user.openId, my_user.groupId))
  218. MoniUser.get_or_create(
  219. moniOpenId = manager_openid, moniAppId = wechat_mp_proxy.appid,
  220. openId = my_user.openId, subLogicalCode = dev.logicalCode, subDealerId = dev.ownerId,
  221. subAgentId = dev.owner.agentId, subPoint = "scabCode", checkTime = datetime.datetime.now())
  222. return FollowGZHResponseRedirect(agentId = str(product_agent.id))
  223. if manager_openid and my_user.nickname:
  224. return None
  225. if auth_bridge.appid == manager_auth_bridge.appid:
  226. if dev.owner.show_auth_window:
  227. return ExternalResponseRedirect(
  228. manager_auth_bridge.generate_auth_url_user_scope(
  229. redirect_uri = USER_AUTH_REDIRECT_URL.WECHAT_MANAGER_AUTH_USER,
  230. payload = state.encode()))
  231. else:
  232. return None
  233. else:
  234. if manager_openid:
  235. if dev.owner.show_auth_window:
  236. return ExternalResponseRedirect(
  237. manager_auth_bridge.generate_auth_url_user_scope(
  238. redirect_uri = USER_AUTH_REDIRECT_URL.WECHAT_MANAGER_AUTH_USER,
  239. payload = state.encode()))
  240. else:
  241. return None
  242. else:
  243. return ExternalResponseRedirect(
  244. manager_auth_bridge.generate_auth_url_base_scope(
  245. redirect_uri = USER_AUTH_REDIRECT_URL.WECHAT_MANAGER_AUTH_BASE,
  246. payload = state.encode()))
  247. def login_required(func = None, error_response = None):
  248. if func is None: return partial(login_required, error_response = error_response)
  249. @wraps(func)
  250. def wrapper(request, *args, **kwargs):
  251. if not supported_app(request):
  252. return NotSupportedPlatformResponseRedirect()
  253. is_right_role = lambda r: r == request.user.__class__.__name__
  254. if hasattr(request, 'user') and request.user:
  255. if (request.user.is_authenticated()) and (any(map(is_right_role, (ROLE.myuser)))):
  256. return func(request, *args, **kwargs)
  257. if error_response is None:
  258. return JsonResponse({"payload": {}}, status = 401)
  259. else:
  260. return error_response() if callable(error_response) else error_response
  261. return wrapper
  262. def user_balance_cache(openId):
  263. # type:(str)->str
  264. return 'balance_{openId}'.format(openId = openId)
  265. def user_last_time_use_ended_cache(openId, logicalCode):
  266. # type:(str, str)->str
  267. return 'last_time_use_{openId}_{logicalCode}'.format(openId = openId, logicalCode = logicalCode)
  268. def moyaya_half_price_cache(openId, logicalCode):
  269. # type:(str, str)->str
  270. return 'half_price_{openId}_{logicalCode}'.format(openId = openId, logicalCode = logicalCode)
  271. def parse_auth_payload(request):
  272. # type: (WSGIRequest)->UserAuthState
  273. state_str = request.GET.get('payload', None)
  274. if state_str is None:
  275. raise InvalidParameter(u'所传参数为空')
  276. state = UserAuthState.decode(state_str)
  277. if not state.is_valid():
  278. raise InvalidParameter(u'网络开小差了, 重新扫码试试喔(%s)' % ErrorCode.USER_STATE_IS_NOT_VALID)
  279. return state
  280. def calc_real_package(openId, logicalCode, package):
  281. coins = VirtualCoin(package['coins'])
  282. money = RMB(package['price'])
  283. if VirtualCoin(serviceCache.get(moyaya_half_price_cache(openId, logicalCode), -1)) == coins:
  284. money = money * Decimal('0.50')
  285. return coins, money
  286. class OrderBuilderContext(object):
  287. def __init__(self, user, device, group, pay_gateway):
  288. self.user = user
  289. self.device = device
  290. self.group = group
  291. self.pay_gateway = pay_gateway
  292. self.dealer = self.device_or_group.owner
  293. @property
  294. def device_or_group(self):
  295. return self.device or self.group
  296. class RechargeRecordBuilder(object):
  297. @staticmethod
  298. def _new_recharge_record(context, out_trade_no, via, money, coins, subject, attachParas, **kwargs):
  299. # type: (OrderBuilderContext, str, str, RMB, VirtualCoin, basestring, Dict, Dict)->RechargeRecord
  300. extra_info = kwargs.pop('extraInfo', {})
  301. extra_info.update({
  302. 'dealerId': context.device_or_group.ownerId,
  303. 'agentId': context.user.agentId
  304. })
  305. if money <= RMB(0):
  306. raise ServiceException({'result': 0, 'description': u'支付金额不得小于或等于0'})
  307. if context.pay_gateway:
  308. gateway_type = context.pay_gateway.gateway_type
  309. assert gateway_type in AppPlatformType.choices(), 'invalid pay gateway type'
  310. pay_app_type = context.pay_gateway.pay_app_type
  311. assert pay_app_type in PayAppType.choices(), 'invalid pay app type'
  312. payload = {
  313. 'orderNo': out_trade_no,
  314. 'ownerId': context.device_or_group.ownerId,
  315. 'money': money,
  316. 'coins': coins,
  317. 'wxOrderNo': '',
  318. 'result': RechargeRecord.PayResult.UNPAY,
  319. 'via': via,
  320. 'subject': subject,
  321. 'attachParas': attachParas,
  322. 'extraInfo': extra_info
  323. }
  324. payload.update(context.device_or_group.identity_info)
  325. payload.update(**kwargs)
  326. try:
  327. return RechargeRecord.issue_pay_record(context=context, **payload)
  328. except NotUniqueError:
  329. raise ServiceException({'result': 0, 'description': u'您已发起支付'})
  330. @staticmethod
  331. def new_recharge_record(payParam):
  332. # type: (PayParam)->RechargeRecord
  333. user = payParam._payer
  334. device = payParam._device
  335. group = payParam._group
  336. ruleId = payParam._ruleId
  337. attachParas = payParam._attachParas
  338. pay_gateway = payParam._payGateway
  339. if not group.rule_is_accepted(ruleId):
  340. raise ServiceException({'result': 0, 'description': u'组内未找到所提供ruleId'})
  341. money = RMB(ruleId)
  342. coins = VirtualCoin(group['ruleDict'][ruleId])
  343. if coins <= VirtualCoin(0):
  344. raise ServiceException({'result': 0, 'description': u'充值金币数额不得小于或等于0'})
  345. logger.info(
  346. 'make new recharge order. gateway = %s; user = %s; ruleId = %s, money = %s, attachParas = %s'
  347. % (pay_gateway.gateway_type, repr(user), ruleId, money, attachParas))
  348. order_no = OrderNoMaker.make_order_no_32(
  349. identifier = device.logicalCode, main_type = OrderMainType.PAY, sub_type = UserPaySubType.RECHARGE)
  350. context = OrderBuilderContext(user = user, device = device, group = group, pay_gateway = pay_gateway)
  351. return RechargeRecordBuilder._new_recharge_record(context=context,
  352. out_trade_no = order_no,
  353. via = USER_RECHARGE_TYPE.RECHARGE,
  354. money = money,
  355. coins = coins,
  356. subject = payParam.subject,
  357. attachParas = attachParas,
  358. **{
  359. 'isQuickPay': False,
  360. 'selectedQuickPayPackageId': None
  361. })
  362. @staticmethod
  363. def new_start_device_record(payParam):
  364. consumeOrder = payParam._order # type: ConsumeRecord
  365. pay_gateway = payParam._payGateway
  366. attachParas = payParam._attachParas
  367. order_no = OrderNoMaker.make_order_no_32(
  368. identifier=consumeOrder.logicalCode,
  369. main_type=OrderMainType.PAY,
  370. sub_type=UserPaySubType.QUICK_PAY
  371. )
  372. context = OrderBuilderContext(
  373. user=consumeOrder.user,
  374. device=consumeOrder.device,
  375. group=consumeOrder.group,
  376. pay_gateway=pay_gateway
  377. )
  378. return RechargeRecordBuilder._new_recharge_record(
  379. context=context,
  380. out_trade_no=order_no,
  381. via=USER_RECHARGE_TYPE.START_DEVICE,
  382. money=consumeOrder.price,
  383. coins=consumeOrder.price,
  384. subject=payParam.subject,
  385. attachParas=attachParas,
  386. **{
  387. 'isQuickPay': True,
  388. })
  389. @staticmethod
  390. def new_consume_order_recharge_record(payParam):
  391. # type: (PayParam)->RechargeRecord
  392. consumeRecord = payParam._consumeRecord
  393. user = payParam._payer
  394. device = payParam._device
  395. group = payParam._group
  396. attachParas = payParam._attachParas
  397. pay_gateway = payParam._payGateway
  398. money = RMB(consumeRecord.price)
  399. coins = VirtualCoin(consumeRecord.price)
  400. logger.info('make charge consume order. user = %s; money = %s' % (repr(user), money))
  401. order_no = OrderNoMaker.make_order_no_32(
  402. identifier=device.logicalCode,
  403. main_type=OrderMainType.PAY,
  404. sub_type=UserPaySubType.CASH_PAY
  405. )
  406. attachParas.update({"orderNo": consumeRecord.orderNo})
  407. context = OrderBuilderContext(user=user, device=device, group=group, pay_gateway=pay_gateway)
  408. return RechargeRecordBuilder._new_recharge_record(
  409. context=context,
  410. out_trade_no=order_no,
  411. via=USER_RECHARGE_TYPE.RECHARGE_CASH,
  412. money=money,
  413. coins=coins,
  414. subject=payParam.subject,
  415. attachParas=attachParas
  416. )
  417. @staticmethod
  418. def new_card_recharge_record(payParam):
  419. card = payParam._card
  420. user = payParam._payer
  421. device = payParam._device
  422. group = payParam._group
  423. attachParas = payParam._attachParas
  424. pay_gateway = payParam._payGateway
  425. attachParas.update({
  426. 'cardNo': card.cardNo
  427. })
  428. if card.id: # 直接设备充值的时候构造了一个内存Card, id为空
  429. attachParas.update({
  430. 'cardId': str(card.id),
  431. })
  432. money = RMB(attachParas.get('price', 0))
  433. coins = VirtualCoin(attachParas.get('coins'))
  434. logger.info('make charge card order. user = %s; money = %s' % (repr(user), money))
  435. order_no = OrderNoMaker.make_order_no_32(identifier=str(attachParas['cardNo']),
  436. main_type=OrderMainType.PAY,
  437. sub_type=UserPaySubType.CARD_RECHARGE)
  438. context = OrderBuilderContext(user=user, device=device, group=group, pay_gateway=pay_gateway)
  439. return RechargeRecordBuilder._new_recharge_record(context=context,
  440. out_trade_no=order_no,
  441. via=USER_RECHARGE_TYPE.RECHARGE_CARD,
  442. money=money,
  443. coins=coins,
  444. subject=payParam.subject,
  445. attachParas=attachParas)
  446. class BatteryInfo(object):
  447. TOKEN_URL = "http://litapi.gmiot.net/1/auth/access_token?"
  448. TRACK_URL = "http://litapi.gmiot.net/1/devices/tracking?"
  449. ACCOUNT = "15595278025"
  450. PW = 'aqkj168'
  451. TOKEN_FIELD_NAME = "access_token"
  452. STATUS_FIELD_NAME = "status"
  453. TOKEN_CACHE_PRE = "AQ_API_TOKEN"
  454. TOKEN_EXPIRE_TIME = 3600
  455. @staticmethod
  456. def myMd5(data):
  457. return hashlib.md5(data.encode("utf-8")).hexdigest().lower()
  458. @staticmethod
  459. def getTokenSign(ntime, pw):
  460. return BatteryInfo.myMd5(BatteryInfo.myMd5(pw) + ntime)
  461. @staticmethod
  462. def sendRequest(url, timeout = 3):
  463. try:
  464. result = requests.post(url = url, timeout = timeout)
  465. if result.status_code != 200:
  466. raise Exception(u"response status is not equal 200")
  467. except Exception as e:
  468. logger.info("api request error, reason is %s" % e)
  469. raise e
  470. jsonData = result.json()
  471. if jsonData.get("ret") != 0:
  472. logger.error("api request return error ret code ret is %s and msg is %s" % (
  473. jsonData.get("ret"), jsonData.get("msg")))
  474. return jsonData
  475. @staticmethod
  476. def joinUrl(url, **kwargs):
  477. from six.moves.urllib import parse
  478. query_params = parse.urlencode(kwargs)
  479. return "{}{}".format(url, query_params)
  480. @staticmethod
  481. def decodeStatus(status):
  482. # 第七到八个字节是电池电压信息 单位0.01v
  483. return int(status[12: 14], 16) + (int(status[14:16], 16) * 0.01)
  484. @property
  485. def token(self):
  486. # 缓存获取token
  487. token = serviceCache.get(self.TOKEN_CACHE_PRE)
  488. if not token:
  489. ntime = str(int(time.time()))
  490. url = self.joinUrl(self.TOKEN_URL, account = self.ACCOUNT, time = ntime,
  491. signature = self.getTokenSign(ntime, self.PW))
  492. data = self.sendRequest(url)
  493. token = data.get(self.TOKEN_FIELD_NAME)
  494. serviceCache.set(self.TOKEN_CACHE_PRE, token, 3600)
  495. return token
  496. def getBatteryInfo(self, imeis):
  497. """ 获取电池状态 """
  498. url = self.joinUrl(self.TRACK_URL, access_token = self.token, imeis = imeis)
  499. result = dict()
  500. data = self.sendRequest(url = url).get("data")
  501. if not data:
  502. return dict()
  503. for item in data:
  504. # 添加电压解析信息
  505. item.update({"voltage": self.decodeStatus(item.get(self.STATUS_FIELD_NAME))})
  506. result.update(
  507. {
  508. item.get("imei"): item
  509. }
  510. )
  511. return result
  512. def __call__(self, imeis):
  513. if isinstance(imeis, list):
  514. imeis = ",".join(imeis)
  515. return self.getBatteryInfo(imeis)
  516. batteryInfo = BatteryInfo()
  517. class IsNeedRenew(object):
  518. """
  519. 虚拟卡是否即将过期
  520. """
  521. def __init__(self, userCard = None):
  522. self._userCard = userCard
  523. def check_renew(self):
  524. if not self._userCard or not self._userCard.cardTypeId or self._userCard.continueTime:
  525. return False, -1
  526. cardType = self.cardType
  527. # 有的暂停发售的卡也需要能够续卡
  528. if cardType is None or (not cardType.renewIgnoreStatus and cardType.status != 1) or (
  529. cardType.renewIgnoreStatus and cardType.status not in [0, 1]):
  530. return False, -1
  531. nowTime = datetime.datetime.now()
  532. leftDays = (self._userCard.expiredTime - nowTime).days
  533. if -cardType.needRenewMax < leftDays < cardType.needRenewMin:
  534. return True, leftDays
  535. return False, -1
  536. @property
  537. def cardType(self):
  538. """
  539. 快速查询, 考虑是否需要用缓存实现
  540. :return:
  541. """
  542. # TODO 缓存实现
  543. cardTypeId = self._userCard.cardTypeId
  544. _cardType = VirtualCard.objects.filter(id = cardTypeId).first()
  545. return _cardType
  546. def __call__(self, userCard):
  547. self._userCard = userCard
  548. return self.check_renew()
  549. is_need_renew = IsNeedRenew()
  550. def check_user_tel(user):
  551. return False if user.phone else True
  552. # 校验用户是否绑定了校园卡
  553. def check_user_bind_card(user):
  554. return False if user.extra.get('campus_user_num', None) else True
  555. def check_black_user(dealerId, openId):
  556. return BlackListUsers.check_black(openId = openId, dealerId = dealerId)
  557. def user_pay_coin(openId, devNo, groupId, ownerId, payCount):
  558. """
  559. 对用户的金币进行扣款 逐个地址进行扣除
  560. """
  561. deduct_list = []
  562. try:
  563. Accounting.recordNetPayCoinCount(devNo)
  564. first_user = MyUser.objects(openId = openId, groupId = groupId).get()
  565. first_user.update(inc__total_consumed = payCount)
  566. user_list = [first_user]
  567. group_list = [str(_.id) for _ in Group.objects(ownerId = ownerId, id__ne = groupId).only('id')]
  568. last_users = MyUser.objects(openId = openId, balance__gt = 0.0, groupId__in = group_list)
  569. user_list += list(last_users)
  570. should_pay = VirtualCoin(payCount)
  571. for user in user_list: # type: MyUser
  572. min_pay = min(VirtualCoin(user.balance), VirtualCoin(should_pay))
  573. deduct_list.append({
  574. 'id': str(user.id),
  575. 'field': 'balance',
  576. 'before': VirtualCoin(user.balance).mongo_amount,
  577. 'coins': min_pay.mongo_amount
  578. })
  579. user.pay(coins=min_pay)
  580. should_pay -= min_pay
  581. if should_pay <= VirtualCoin(0):
  582. break
  583. # 当前经销商下的所有余额都扣完了,还不够,就要扣代理商下的了(实际也只有代理商支持代理商扣费,才可能到此分支)
  584. # TODO 这里明显隐含了关系,需要显示出来
  585. if should_pay > VirtualCoin(0):
  586. my_users = MyUser.objects.filter(openId = openId, balance__gt = 0.0)
  587. agentDict = defaultdict()
  588. for user in my_users:
  589. if user.is_product_user():
  590. # TODO 目前自定义公众号进入构成的用户并不记录任何金额信息, 所以直接跳过
  591. continue
  592. tempGroup = Group.get_group(user.groupId)
  593. if tempGroup:
  594. groupDealer = Dealer.objects(id = tempGroup['ownerId']).get()
  595. if tempGroup['ownerId'] in agentDict:
  596. groupAgentId = agentDict[tempGroup['ownerId']]
  597. if groupDealer.agentId != groupAgentId:
  598. continue
  599. else:
  600. groupAgentId = groupDealer.agentId
  601. agentDict[tempGroup['ownerId']] = groupDealer.agentId
  602. min_pay = min(user.balance, should_pay) # type: VirtualCoin
  603. deduct_list.append({
  604. 'id': str(user.id),
  605. 'field': 'balance',
  606. 'before': VirtualCoin(user.balance).mongo_amount,
  607. 'coins': min_pay.mongo_amount
  608. })
  609. user.pay(coins = min_pay)
  610. should_pay -= min_pay
  611. if should_pay <= VirtualCoin(0):
  612. break
  613. if should_pay > VirtualCoin(0) or should_pay < VirtualCoin(0):
  614. raise UserServerException(u'支付扣除金币异常')
  615. except UserServerException, e:
  616. logger.error('user<openId={}> has not enough coins to pay for {}. something is wrong.'.format(openId, devNo))
  617. ExceptionLog.log(
  618. user = '{}<openId={}>'.format(MyUser.__class__.__name__, openId),
  619. exception = e.message,
  620. extra = {'devNo': devNo, 'deduct': deduct_list})
  621. except Exception as e:
  622. logger.exception(e)
  623. ExceptionLog.log(
  624. user = '{}<openId={}>'.format(MyUser.__class__.__name__, openId),
  625. exception = traceback.format_exc(),
  626. extra = {'devNo': devNo, 'deduct': deduct_list})
  627. def freeze_user_balance(device, group, order, virtual_card = None):
  628. # type: (DeviceDict, GroupDict, ConsumeRecord, UserVirtualCard)->bool
  629. if order.paymentInfo['via'] == 'free':
  630. return True
  631. elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
  632. MyUser.freeze_balance(str(order.id), order.paymentInfo['deduct'])
  633. elif order.paymentInfo['via'] == 'virtualCard':
  634. if not virtual_card:
  635. virtual_card = UserVirtualCard.objects(id = str(order.paymentInfo['itemId'])).first() # type: UserVirtualCard
  636. if virtual_card:
  637. virtual_card.freeze_quota(order.package, str(order.id))
  638. else:
  639. raise ServiceException({'result': 0, 'description': u"不支持的支付类型"})
  640. def recover_user_frozen_balance(device, group, order, virtual_card = None):
  641. # type: (DeviceDict, GroupDict, ConsumeRecord, UserVirtualCard)->bool
  642. if order.paymentInfo['via'] == 'free':
  643. return True
  644. elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
  645. MyUser.recover_frozen_balance(str(order.id), order.paymentInfo['deduct'])
  646. elif order.paymentInfo['via'] == 'virtualCard':
  647. if not virtual_card:
  648. virtual_card = UserVirtualCard.objects(
  649. id = str(order.paymentInfo['itemId'])).first() # type: UserVirtualCard
  650. if virtual_card:
  651. virtual_card.recover_frozen_quota(str(order.id))
  652. else:
  653. raise ServiceException({'result': 0, 'description': u"不支持的支付类型"})
  654. def clear_frozen_user_balance(device, order, usedTime, spendElec, backCoins, user = None, virtual_card = None):
  655. # type: (DeviceDict, ConsumeRecord, float, float, VirtualCoin, MyUser, UserVirtualCard)->bool
  656. if order.paymentInfo['via'] == 'free':
  657. return True
  658. elif order.paymentInfo['via'] in ['netPay', 'coins', 'cash', 'coin']:
  659. if not user:
  660. user = MyUser.objects(openId = order.openId, groupId = order.groupId).first() # type: MyUser
  661. if user:
  662. user.clear_frozen_balance(str(order.id), order.paymentInfo['deduct'], backCoins, order.coin)
  663. elif order.paymentInfo['via'] == 'virtualCard':
  664. if not virtual_card:
  665. virtual_card = UserVirtualCard.objects(
  666. id = str(order.paymentInfo['itemId'])).first() # type: UserVirtualCard
  667. if virtual_card:
  668. virtual_card.clear_frozen_quota(str(order.id), usedTime, spendElec, backCoins)
  669. else:
  670. raise ServiceException({'result': 0, 'description': u"不支持的支付类型"})
  671. def vCard_pay_coin(order, vCard, dev, group, package, attachParas, user = None):
  672. # type:(ConsumeRecord, UserVirtualCard, DeviceDict, GroupDict, dict, dict, MyUser)->None
  673. devNo = dev.devNo
  674. groupId = group['groupId']
  675. if not user:
  676. try:
  677. user = MyUser.objects(openId = order.openId, groupId = groupId).get()
  678. except DoesNotExist as e:
  679. logger.error('cannot find user openId=%s, groupId=%s' % (order.openId, groupId))
  680. raise e
  681. nickname = user.nickname
  682. # 扣除卡的额度,并把缓存记录到缓存中
  683. consumeRcd = vCard.consume(openId = order.openId, group = group, dev = dev, package = package,
  684. attachParas = attachParas, nickname = nickname)
  685. if not consumeRcd:
  686. raise ServiceException({'result': 2, 'description': u"扣除虚拟卡额度的时候,出现异常,请重试"})
  687. else:
  688. # TODO 巨大的错误 对于冻结的方式来说 ,itemId 指的是卡Id 对于pay 方式来说 这个id又指的是 消费id 指代不一致 容易导致问题
  689. # TODO 这个地方重新修改 统一规定为 卡的ID 将代码全部检查一遍 之前定义为rcd id的全部重写逻辑
  690. order.paymentInfo = {
  691. 'itemId': str(vCard.id),
  692. 'via': "virtualCard",
  693. "money": RMB(order.money).mongo_amount,
  694. "coins": VirtualCoin(order.coin).mongo_amount,
  695. "rcdId": str(consumeRcd.id),
  696. }
  697. order.save()
  698. cInfo = Device.get_dev_control_cache(devNo)
  699. if attachParas.has_key('chargeIndex'):
  700. port = attachParas.get('chargeIndex')
  701. if cInfo.has_key(port):
  702. cInfo[port].update({'consumeRcdId': str(consumeRcd.id)})
  703. Device.update_dev_control_cache(devNo, cInfo)
  704. return consumeRcd
  705. BUILDER_MAP = {
  706. "RECHARGE": RechargeRecordBuilder.new_recharge_record,
  707. "START_DEVICE": RechargeRecordBuilder.new_start_device_record,
  708. "RECHARGE_ORDER": RechargeRecordBuilder.new_consume_order_recharge_record,
  709. "RECHARGE_CARD": RechargeRecordBuilder.new_card_recharge_record,
  710. }
  711. class PayParam(object):
  712. """
  713. 用户进入 payGateway 的时候 对于前台入参的解析校验
  714. 所有的拉起支付,必定需要传入logicalCode或者groupId以及type参数
  715. """
  716. def __init__(self, request):
  717. # type: (WSGIRequest)->None
  718. self._request = request
  719. self._clientName = self._get_client_env(request)
  720. self._ua = request.META.get('HTTP_USER_AGENT')
  721. self._payer = request.user
  722. self._payload = self._parse_payload(request)
  723. self._recordBuilderKey = None
  724. self._consumeRecord = None
  725. self._vCard = None
  726. self._card = None
  727. self._order = None
  728. self._payType = self._payload.get("type", None)
  729. self._attachParas = self._fix_attach_paras(self.payload.get('attachParas', dict()))
  730. if self._payload.get('logicalCode', None):
  731. self._device = Device.get_dev_by_l(self._payload.get('logicalCode')) # type: DeviceDict
  732. self._group = self._device.group # type: GroupDict
  733. self._dealer = self._device.owner # type: Dealer
  734. elif self._payload.get('groupId', None):
  735. self._device = None # type: Optional[DeviceDict]
  736. self._group = Group.get_group(self._payload.get('groupId')) # type: GroupDict
  737. self._dealer = self._group.owner # type: Dealer
  738. elif self._attachParas.get("orderNo"):
  739. order = get_consume_order(self._attachParas["orderNo"]) # type: ConsumeRecord
  740. self._device = order.device
  741. self._group = order.group
  742. self._dealer = order.group.owner
  743. else:
  744. raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1001)'})
  745. self.common_check()
  746. self._payGateway = None
  747. self._subject = None
  748. self._source = None
  749. self._money = None
  750. self._coins = None
  751. self._discountInfo = None
  752. self._monthlyPackage = None
  753. if self._payType == 'startDevice':
  754. self.handle_param_with_start_device()
  755. elif self._payType in ['charge', 'forceCharge']:
  756. self.handle_param_with_recharge()
  757. elif self._payType == "orderPay":
  758. self.handle_param_with_order()
  759. elif self._payType == "chargeCard":
  760. self.handle_param_with_card()
  761. else:
  762. logger.error(
  763. "error pay type, payload is <{}>, attachParas is <{}>".format(self._payload, self._attachParas)
  764. )
  765. raise ServiceException({'result': 0, 'description': u'设备投放地址不属于该平台,请选择其他地址。'})
  766. product_agent = get_user_manager_agent(self._dealer)
  767. if self._payer.productAgentId != str(product_agent.id):
  768. logger.debug('product of {} is {}. is not belong to {}'.format(repr(self._dealer), repr(product_agent), repr(self._payer)))
  769. raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1002)'})
  770. self._product_agent = product_agent # type: Agent
  771. # 获取支付网关
  772. self.load_pay_gateway()
  773. @property
  774. def UA(self):
  775. return self._ua
  776. @property
  777. def clientName(self):
  778. return self._clientName
  779. @property
  780. def _ruleId(self):
  781. return self._payload.get('ruleId', None) or self._attachParas.get('ruleId', None)
  782. @staticmethod
  783. def _fix_attach_paras(attachParas):
  784. # type: (dict)->dict
  785. return fix_dict(attachParas, ['chargeIndex'])
  786. def common_check(self):
  787. """
  788. 支付前的公共参数校验 通用的校验都放在这个里面
  789. 目前校验的
  790. 1. 是否是拉黑用户 拉黑用户一律不允许充值
  791. 2. 如果用户是个人中心的充值行为 校验是否是该平台下的用户
  792. :return:
  793. """
  794. # 经销商状态异常
  795. if not self._dealer.normal:
  796. raise ServiceException({"result": 0, "description": u"设备经销商账号异常,暂时不能支付。"})
  797. # 对于用户拉黑的处理
  798. if check_black_user(dealerId = str(self._dealer.id), openId = self._payer.openId):
  799. raise ServiceException({"result": 0, "description": u"暂不支持支付,请联系设备经销商解决。"})
  800. if not self._group:
  801. raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1002)'})
  802. # 校验组owner是否一致
  803. if self._group.ownerId != str(self._dealer.id):
  804. raise ServiceException({"result": 0, "description": u"设备参数错误,无法支付,请联系设备经销商解决(1001)。"})
  805. # 校验设备owner是否一致
  806. if self._device and self._device.ownerId != str(self._dealer.id):
  807. raise ServiceException({"result": 0, "description": u"设备参数错误,无法支付,请联系设备经销商解决(1002)。"})
  808. if not self._payType:
  809. raise ServiceException({'result': 0, 'description': u'参数错误,请刷新重试(1002)'})
  810. def call_check(self):
  811. """
  812. 拉起支付检查. 支付作为唯一检测点
  813. :return:
  814. """
  815. if self._dealer.supports("support_collect_room_info"):
  816. myUserDetail = MyUserDetail.objects(openId = str(self._payer.openId),
  817. dealerId = str(self._dealer.id)).first()
  818. if not (myUserDetail and
  819. myUserDetail.userName and
  820. myUserDetail.userPhone and
  821. myUserDetail.userUnit and
  822. myUserDetail.userFloor and
  823. myUserDetail.userRoom):
  824. raise InterceptException(redirect = before_frag_add_query(
  825. concat_front_end_url(uri = '/user/index.html#/user/roomVerify'),
  826. added_query = {
  827. 'ownerId': str(self._dealer.id),
  828. 'userName': myUserDetail and myUserDetail.userName or '',
  829. 'userPhone': myUserDetail and myUserDetail.userPhone or '',
  830. 'userUnit': myUserDetail and myUserDetail.userUnit or '',
  831. 'userFloor': myUserDetail and myUserDetail.userFloor or '',
  832. 'userRoom': myUserDetail and myUserDetail.userRoom or '',
  833. }))
  834. @staticmethod
  835. def _get_client_env(request):
  836. if detect_wechat_client(request):
  837. return "wechat"
  838. elif detect_alipay_client(request):
  839. return "alipay"
  840. elif detect_jdpay_client(request) or detect_jdjr_client(request):
  841. return "jd"
  842. else:
  843. raise ServiceException({"result": 0, "description": u"不支持的客户端"})
  844. def _check_big_package(self, package):
  845. if RMB(package['price']) > self._dealer.maxPackagePrice:
  846. raise ServiceException({'result': 0, 'description': u'套餐支付金额超限'})
  847. def _check_big_charge(self, money):
  848. agent = Agent.objects.get(id = self._dealer.agentId) # type: Agent
  849. max_pay_limit = int(agent.maxPayLimit)
  850. if money > RMB(max_pay_limit):
  851. raise ServiceException({'result': 0, 'description': u'支付金额超限(%d)' % ErrorCode.TOO_BIG_RECHARGE})
  852. def handle_param_with_recharge(self):
  853. """
  854. 用户直接充值余额的参数处理
  855. 对于用户的充值
  856. :return:
  857. """
  858. self._recordBuilderKey = "RECHARGE"
  859. ruleId = self._ruleId
  860. if not self._group.rule_is_accepted(ruleId):
  861. raise ServiceException({'result': 0, 'description': u'组内未找到所提供ruleId'})
  862. money = RMB(ruleId)
  863. coins = VirtualCoin(self._group['ruleDict'][ruleId])
  864. self._check_big_charge(money)
  865. if coins <= VirtualCoin(0):
  866. raise ServiceException({'result': 0, 'description': u'充值金币数额必须大于0金币'})
  867. if money <= RMB(0):
  868. raise ServiceException({'result': 0, 'description': u'支付金额必须大于0元'})
  869. self._money = money
  870. self._coins = coins
  871. self._subject = gen_recharge_purchase_subject(
  872. major_type=self._device.majorDeviceType[0:8],
  873. logicalCode=self._device.logicalCode
  874. )
  875. if self._payType == 'charge' and 'startKey' in self._attachParas:
  876. try:
  877. actionBox = ActionDeviceBuilder.create_action_device(self._device)
  878. actionBox.check_dev_status(self._attachParas)
  879. except ServiceException as e:
  880. logger.info('device(devNo=%s)(via %s)recharge failure. response==%s' % (
  881. self._device.devNo, self._payGateway.gateway_type, e.result['description'].encode('utf-8'),))
  882. raise ServiceException({'result': 103, 'description': e.result['description'].encode('utf-8')})
  883. def handle_param_with_order(self):
  884. """
  885. 订单的后支付流程 这个地方其实可以和start_device统一
  886. :return:
  887. """
  888. consumeRecordId = self._attachParas.get("consumeRecordId", "")
  889. consumeRecord = ConsumeRecord.objects.filter(id=consumeRecordId).first()
  890. if not consumeRecord:
  891. raise ServiceException({"result": 0, "description": u"查询消费记录失败,请刷新页面后再尝试支付"})
  892. if consumeRecord.status != consumeRecord.Status.END:
  893. raise ServiceException({"result": 0, "description": u"当前订单状态不允许支付"})
  894. if self._payer.openId != consumeRecord.openId:
  895. raise ServiceException({'result': 0, 'description': u'不是您消费的订单,无需为此支付费用'})
  896. if self._group.groupId != consumeRecord.groupId:
  897. raise ServiceException({'result': 0, 'description': u'支付失败,请联系经销商或客服'})
  898. self._consumeRecord = consumeRecord
  899. # 将运行结束的订单 切换为 等待支付的状态
  900. ConsumeOrderStateEngine(consumeRecord).to_wait_pay()
  901. self._recordBuilderKey = "RECHARGE_ORDER"
  902. self._subject = gen_quick_pay_purchase_subject(
  903. major_type = self._device.majorDeviceType[0:8],
  904. logicalCode = self._device.logicalCode,
  905. port = self.selected_port)
  906. def handle_param_with_start_device(self):
  907. self._recordBuilderKey = "START_DEVICE"
  908. orderNo = self.attachParas.get("orderNo")
  909. order = get_consume_order(orderNo) # type: ConsumeRecord
  910. if not order:
  911. raise ServiceException({'result': 0, 'description': u'消费订单查询失败'})
  912. if order.status != order.Status.WAIT_CONF:
  913. raise ServiceException({"result": 2, "description": u"订单状态异常,请前往个人中心查看订单"})
  914. self._order = order
  915. self._check_big_charge(order.price)
  916. if self._order.price <= RMB(0):
  917. raise ServiceException({'result': 0, 'description': u'支付金额必须大于0元'})
  918. self._subject = gen_recharge_purchase_subject(
  919. major_type=order.device.majorDeviceType[0:8],
  920. logicalCode=order.logicalCode
  921. )
  922. def handle_param_with_card(self):
  923. self._recordBuilderKey = "RECHARGE_CARD"
  924. self._money = RMB(self._attachParas["price"])
  925. self._coins = VirtualCoin(self._attachParas["coins"])
  926. cardId = self._attachParas.get("cardId")
  927. if not cardId:
  928. raise ServiceException({"result": 0, "description": u"该卡不存在,请联系平台客服处理(1000)"})
  929. card = Card.objects.filter(id=str(cardId)).first()
  930. if not card:
  931. logger.error('card<id={}> not find'.format(cardId))
  932. raise ServiceException({'result': 0, 'description': u'该卡不存在,请联系平台客服处理(1001)'})
  933. if not card.dealerId or not card.groupId:
  934. logger.error('card<id={}> not bind dealer or group'.format(cardId))
  935. raise ServiceException({
  936. 'result': ErrorCode.CARD_NEED_BIND_GROUP,
  937. 'description': u'该卡未绑定开卡地址',
  938. 'payload': {
  939. 'cardId': str(card.id),
  940. 'cardName': card.cardName,
  941. 'phone': card.phone,
  942. 'cardNo': card.cardNo
  943. }})
  944. if self._group.ownerId != card.dealerId:
  945. raise ServiceException({'result': 0, 'description': u'该卡不存在,请联系平台客服(1002)'})
  946. self._card = card
  947. self._subject = cn(u"实体卡充值 {}".format(card.cardNo))
  948. def load_pay_gateway(self):
  949. """
  950. 加载支付网关 目前不支持商户的模式
  951. """
  952. source = self._product_agent
  953. visitor = AgentCustomizedBrandVisitor
  954. self._source = source
  955. funName = "get_{}_env_pay_gateway".format(self._clientName)
  956. get_env_pay_gateway_func = import_string("apps.web.helpers.{}".format(funName))
  957. payGateway = get_env_pay_gateway_func(source, role = ROLE.myuser, vistor = visitor)
  958. self._payGateway = payGateway
  959. def to_dict(self):
  960. return {
  961. "user": self._payer,
  962. "consumeRecord": self._consumeRecord,
  963. "vCard": self._vCard,
  964. "card": self._card,
  965. "device": self._device,
  966. "group": self._group,
  967. "dealer": self._dealer,
  968. "agent": self._product_agent,
  969. "gateWay": self._payGateway
  970. }
  971. def __str__(self):
  972. return "pay info is <{}>".format(self.to_dict())
  973. @property
  974. def payGateway(self):
  975. assert self._payGateway.pay_app_type in PayAppType.choices(), 'invalid pay app type'
  976. return self._payGateway
  977. @property
  978. def recordBuilderKey(self):
  979. assert self._recordBuilderKey in BUILDER_MAP.keys(), 'invalid build key'
  980. return self._recordBuilderKey
  981. @property
  982. def curUser(self):
  983. return self._payer # type: MyUser
  984. @property
  985. def subject(self):
  986. return self._subject or ""
  987. @property
  988. def payload(self):
  989. return self._payload
  990. @property
  991. def attachParas(self):
  992. return self._attachParas
  993. @property
  994. def isQuickPay(self):
  995. return True if self._payload.get("quickPay") else False
  996. @property
  997. def source(self):
  998. return self._source
  999. @property
  1000. def goodsInfo(self):
  1001. info = {}
  1002. info.update({
  1003. "goodsName": cn(self._subject),
  1004. "payPrice": str(self._money),
  1005. "goodsPrice": str(self._money),
  1006. "discountInfo": ""
  1007. })
  1008. return info
  1009. @staticmethod
  1010. def _parse_payload(request):
  1011. """
  1012. 解析支付的参数
  1013. 历史遗留原因 之前的支付参数使用的是query传参 参数键为params
  1014. 现在的预下单接口 使用的是body-json传参
  1015. """
  1016. if request.method == GET:
  1017. return json.loads(request.GET.get("params", '{}'))
  1018. else:
  1019. return json.loads(request.body)
  1020. @property
  1021. def showAd(self):
  1022. return self._dealer.ad_show
  1023. @property
  1024. def selected_port(self):
  1025. if not self._attachParas:
  1026. return None
  1027. if 'chargeIndex' in self._attachParas and \
  1028. self._attachParas['chargeIndex'] and \
  1029. self.attachParas['chargeIndex'] not in ['None', 'null', -1, '-1']:
  1030. return str(self._attachParas['chargeIndex'])
  1031. else:
  1032. return None
  1033. class RedpackBuilder(object):
  1034. @staticmethod
  1035. def redpack_to_ledger(order):
  1036. # type:(ConsumeRecord) -> None
  1037. pay_app_type = order.user.gateway
  1038. payGateway = get_platform_promotion_pay_gateway(pay_app_type = pay_app_type)
  1039. # 红包订单建立 并分账
  1040. redpackId = order.attachParas.get('redpackId')
  1041. redpack = Redpack.get_one(redpackId)
  1042. if not redpack:
  1043. logger.error("redpack is null, is not valid record.")
  1044. return
  1045. redpack_attachParas = order.attachParas
  1046. redpack_attachParas.update({'masterId': order.rechargeRcdId, 'startKey': order.startKey})
  1047. dev = Device.get_dev(order.devNo) # type: DeviceDict
  1048. subject = gen_quick_pay_purchase_subject(
  1049. major_type = dev.majorDeviceType[0:8], logicalCode = order.logicalCode, port = order.used_port)
  1050. context = OrderBuilderContext(user = order.user, device = dev, group = dev.group, pay_gateway = payGateway)
  1051. redpack_recharge_record = RechargeRecordBuilder._new_recharge_record(
  1052. context = context,
  1053. out_trade_no = OrderNoMaker.make_order_no_32(
  1054. identifier = order.logicalCode,
  1055. main_type = OrderMainType.PAY,
  1056. sub_type = UserPaySubType.REDPACK
  1057. ),
  1058. via = USER_RECHARGE_TYPE.RECHARGE_REDPACK,
  1059. money = RMB(redpack['redpackMoney']),
  1060. coins = VirtualCoin(redpack['redpackCoins']),
  1061. subject = subject,
  1062. attachParas = redpack_attachParas,
  1063. **{
  1064. 'isQuickPay': True,
  1065. 'selectedQuickPayPackageId': order.attachParas.get('packageId'),
  1066. 'result': RechargeRecord.PayResult.SUCCESS,
  1067. 'finishedTime': datetime.datetime.now(),
  1068. 'description': '平台红包',
  1069. })
  1070. ledger = Ledger(USER_RECHARGE_TYPE.RECHARGE_REDPACK, redpack_recharge_record)
  1071. ledger.execute(journal = False, stats = True, check = False)
  1072. @staticmethod
  1073. def user_pay_coin_with_redpack(order):
  1074. if Redpack.use_redpack(order.attachParas['redpackId'], str(order.id), order.package):
  1075. redpack = Redpack.get_one(order.attachParas['redpackId'])
  1076. deduct_coins = VirtualCoin(redpack.get('redpackCoins', 0))
  1077. pay_count = round(VirtualCoin(order.package['coins']) - deduct_coins, 2)
  1078. if pay_count > 0:
  1079. user_pay_coin(order.openId, order.devNo, order.groupId, order.ownerId, pay_count)
  1080. try:
  1081. RedpackBuilder.redpack_to_ledger(order)
  1082. except:
  1083. import traceback
  1084. logger.error(traceback.format_exc())
  1085. @staticmethod
  1086. def bt_user_pay_coin_with_redpack(order):
  1087. if Redpack.use_redpack(order.attachParas['redpackId'], str(order.id), order.package):
  1088. redpack = Redpack.get_one(order.attachParas['redpackId'])
  1089. deduct_coins = VirtualCoin(redpack.get('redpackCoins', 0))
  1090. pay_count = VirtualCoin(order.package['coins']) - deduct_coins
  1091. if pay_count > VirtualCoin(0):
  1092. device = Device.get_dev_by_l(order.logicalCode)
  1093. errCode, errMsg = order.user.deduct_fee_for_bt(device, pay_count)
  1094. if errCode != 1:
  1095. raise ServiceException({'result': errCode, 'description': errMsg})
  1096. try:
  1097. RedpackBuilder.redpack_to_ledger(order)
  1098. except:
  1099. import traceback
  1100. logger.error(traceback.format_exc())
  1101. @staticmethod
  1102. def get_alipay_cpa_by_ruhui(openId, logicalCode, urlId='', showType='', **kw):
  1103. if not urlId:
  1104. urlId = ConsumeRecord.make_no()
  1105. kw.update({
  1106. 'delayPay': 'true',
  1107. 'urlId': urlId,
  1108. 'logicalCode': logicalCode
  1109. })
  1110. dev = Device.get_dev_by_l(logicalCode)
  1111. from apps.thirdparties.aliyun import AliRuHui
  1112. RH = AliRuHui()
  1113. extra = RedpackBuilder._connection_string(kw)
  1114. params = {
  1115. 'urlId': urlId,
  1116. # 'channelId': settings.ALIPAY_RUHUI_CHANNEL_ID,
  1117. 'alipayOpenId': openId,
  1118. 'outerCode': logicalCode,
  1119. 'extra': extra,
  1120. 'optionType': '1'
  1121. }
  1122. popUpQueryRequest = RH.PopUpQueryRequest(params) # Type: Dict
  1123. if popUpQueryRequest.get('Status') == True:
  1124. money = RedpackBuilder.calc_by_amount(RMB(popUpQueryRequest.get('UnionAmount', 0)))
  1125. if money == RMB(0):
  1126. return {}
  1127. else:
  1128. import urlparse
  1129. taskId = dict(urlparse.parse_qsl(popUpQueryRequest.get('Url'))).get('taskId')
  1130. redpack = Redpack.create_redpack_by_ruhui(
  1131. taskId=taskId,
  1132. openId=openId,
  1133. urlId=urlId,
  1134. money=money,
  1135. leastPayMoney=RMB(0),
  1136. gateway='alipay',
  1137. logicalCode=logicalCode,
  1138. devNo=dev.devNo,
  1139. extra={'ApiPopUpQueryRequest': popUpQueryRequest},
  1140. showType=showType)
  1141. return {'url': popUpQueryRequest.get('Url'), 'money': redpack.money.mongo_amount,
  1142. 'redpackId': str(redpack.id), 'showType': showType, }
  1143. else:
  1144. return {}
  1145. @staticmethod
  1146. def get_alipay_cpa_by_ruhui_v3(openId, logicalCode, showType='', **kw):
  1147. from apps.thirdparties.aliyun import AlipayYunMaV3
  1148. RH = AlipayYunMaV3()
  1149. dev = Device.get_dev_by_logicalCode(logicalCode)
  1150. result = RH.get_cpa_ruhui_body(openId, logicalCode)
  1151. logger.info(result)
  1152. if result.body.success == True and len(result.body.result.seatbid[0].bid[0].ads):
  1153. seatb = result.body.result.seatbid[0]
  1154. urlId = result.body.result.id
  1155. money = RMB.fen_to_yuan(seatb.bid[0].ads[0].price)
  1156. money = RedpackBuilder.calc_by_amount(money)
  1157. if money == RMB(0):
  1158. return {}
  1159. else:
  1160. import urlparse
  1161. url = seatb.bid[0].ads[0].crurl
  1162. taskId = seatb.bid[0].ads[0].id
  1163. redpack = Redpack.create_redpack_by_ruhui(
  1164. taskId=taskId,
  1165. openId=openId,
  1166. urlId=urlId,
  1167. money=money,
  1168. leastPayMoney=RMB(0),
  1169. gateway='alipay',
  1170. logicalCode=logicalCode,
  1171. devNo=dev.devNo,
  1172. extra={'ApiRequest': json.dumps(result.body.result.to_map(), ensure_ascii=False)},
  1173. showType=showType)
  1174. return {'url': url, 'money': redpack.money.mongo_amount,
  1175. 'redpackId': str(redpack.id), 'showType': showType, 'urlId': urlId}
  1176. else:
  1177. return {}
  1178. @staticmethod
  1179. def _connection_string(kw):
  1180. # type: (dict) -> unicode
  1181. """
  1182. 拼接字符串
  1183. :param kw:
  1184. :return:
  1185. """
  1186. return "&".join("{}={}".format(*_) for _ in kw.items())
  1187. @classmethod
  1188. def calc_by_amount(cls, unionAmount):
  1189. # type: (RMB) -> Optional[RMB, bool]
  1190. """
  1191. 随机金额
  1192. """
  1193. random_list = [0.1, 0.2, 0.2, 0.3, 0.3, 0.3, 0.4, 0.4, 0.5]
  1194. import random
  1195. radio = Ratio(random.choice(random_list))
  1196. return unionAmount * radio
  1197. @classmethod
  1198. def get_alipay_cpa_by_laxin(cls, openId, channelId=None):
  1199. TASKMAPNAME={
  1200. '1': '关注天猫店铺领红包',
  1201. '2': '关注天猫店铺领红包',
  1202. '3': '关注淘宝店铺领红包',
  1203. '4': '关注淘宝店铺领红包',
  1204. '5': '浏览商品任务领红包',
  1205. '6': '浏览商品任务领红包',
  1206. '7': '看直播领红包',
  1207. '8': '看直播领红包',
  1208. }
  1209. from apps.thirdparties.aliyun import AliLaXin
  1210. ali = AliLaXin()
  1211. resp = ali.QueryUnionPromotionRequest({'alipayOpenId': openId, 'channelId': channelId})
  1212. code = resp.get('ErrorCode')
  1213. dataList = []
  1214. if code == 0:
  1215. dataList = resp.get('Result', [])
  1216. for item in dataList:
  1217. item['alipayOpenId'] = openId
  1218. item['channel'] = channelId or settings.ALIPAY_LAXIN_CHANNEL_ID
  1219. item['taskName'] = TASKMAPNAME.get(item.get('taskType', ''), '')
  1220. return dataList
  1221. @classmethod
  1222. def query_cpa_laxin_task_status(cls, taskId, openId):
  1223. from apps.thirdparties.aliyun import AliLaXin
  1224. ali = AliLaXin()
  1225. resp = ali.GetUnionTaskStatusRequest({'taskId': taskId, 'alipayOpenId': openId})
  1226. code = resp.get('ErrorCode')
  1227. result = resp.get('Result')
  1228. success = resp.get('Success')
  1229. if code == 0 and result == True and success == True:
  1230. return True
  1231. else:
  1232. return False
  1233. @classmethod
  1234. def create_cpa_laxin_redpack(cls, dev, taskId, openId, taskType):
  1235. effectTime = datetime.datetime.now()
  1236. expiredTime = datetime.datetime.now() + datetime.timedelta(days=30 * 3)
  1237. return Redpack.create_redpack_by_laxin(factoryCode=taskId, openId=openId,
  1238. money=RMB(0.01), leastPayMoney=RMB(0),
  1239. effectTime=effectTime, expiredTime=expiredTime, gateway='alipay',
  1240. logicalCode=dev.logicalCode, devNo=dev.devNo, extra={'taskType':taskType})
  1241. @staticmethod
  1242. def _set_alipay_key(openId, taskId, urlId, money, showType):
  1243. cache.set('alipayAd{}'.format(openId), {'taskId': taskId, 'urlId': urlId, 'money': money, 'showType': showType})
  1244. @staticmethod
  1245. def _pop_alipay_key(openId):
  1246. result = cache.get('alipayAd{}'.format(openId))
  1247. if result:
  1248. cache.delete('alipayAd{}'.format(openId))
  1249. return result
  1250. def get_consume_order(orderNo): # type: (str) -> Optional[ConsumeRecord, None]
  1251. return ConsumeRecord.objects.filter(Q(orderNo=orderNo) | Q(sequenceNo=orderNo)).first()