utils.py 60 KB

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