utils.py 60 KB

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