utils.py 59 KB

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