# -*- coding: utf-8 -*- # !/usr/bin/env python import datetime import logging import time import simplejson as json from django.conf import settings from django.views.decorators.http import require_POST from typing import TYPE_CHECKING, Optional from apilib.monetary import VirtualCoin from apilib.utils_json import JsonResponse from apps.web.ad.models import Advertisement from apps.web.agent.models import Agent from apps.web.common.proxy import ClientRechargeModelProxy from apps.web.common.transaction.pay import OrderCacheMgr from apps.web.constant import Const, AppPlatformType, PollRecordDefine from apps.web.core import PayAppType, ROLE from apps.web.core.utils import JsonErrorResponse, DefaultJsonErrorResponse, JsonOkResponse from apps.web.dealer.models import Dealer from apps.web.device.models import Device, Group from apps.web.helpers import get_wechat_mini_env_pay_gateway, get_alipay_env_pay_gateway from apps.web.services.bluetooth.service import StartService, FinishService, ActionBtDeviceBuilder from apps.web.user.conf import PAY_NOTIFY_URL from apps.web.user.models import RechargeRecord, MyUser, Redpack from apps.web.user.payments import gen_quick_pay_purchase_subject, gen_recharge_purchase_subject from apps.web.user.utils import RechargeRecordBuilder from apps.web.utils import error_tolerate, detect_wechat_client, detect_alipay_client, permission_required, \ MiniGatewayResponseRedirect, get_alipay_gateway_result from apps.web.wrapper import request_limit_by_user from library.jd.pay import PiType, GatewayMethod from middlewares.django_jwt_session_auth import jwt_session_key from taskmanager.mediator import task_caller logger = logging.getLogger(__name__) if TYPE_CHECKING: from apps.web.core.payment.ali import AliPayGateway from apps.web.device.models import DeviceDict from apps.web.common.proxy import QuerySetProxy @error_tolerate(nil=DefaultJsonErrorResponse) @permission_required(ROLE.myuser) @request_limit_by_user(operation='equipmentPara', limit=50, logger=logger) def equipmentPara(request): logicalCode = request.GET.get('logicalCode', None) try: dev = Device.get_dev_by_logicalCode(logicalCode) # type: DeviceDict if dev is None: logger.error('not find device. logicalCode = %s' % logicalCode) return JsonErrorResponse(description = u'获取数据失败,请刷新后再试') groupId = dev.get('groupId') if not groupId: logger.error('not find group id. dev = %s' % str(dev)) return JsonErrorResponse(description = u'该设备未注册,暂时无法使用网络支付, 请投币(1001)') group = Group.get_group(groupId) if group is None: logger.error('not find group. dev = %s; groupId = %s' % (str(dev), groupId)) return JsonErrorResponse(description = u'该设备未注册,暂时无法使用网络支付, 请投币(1002)') dealer = Dealer.get_dealer(group['ownerId']) if dealer is None: logger.error('not find dealer. dev = %s; group = %s' % (str(dev), str(group))) return JsonErrorResponse(description = u'该设备未注册,暂时无法使用网络支付, 请投币(1003)') if not dev['devType']: logger.error('not find dev type. dev = %s; group = %s' % (str(dev), str(group))) return JsonErrorResponse(description = u'该设备未注册,暂时无法使用网络支付, 请投币(1005)') data = { "devType": dev.get('devType', {}), "groupId": groupId, "online": True, "cityId": group.get('districtId', ''), "inFreeGroup": group.get('isFree', False), # "servicePhone": dealer['servicePhone'], # "serviceName": dealer['serviceName'], # "qrcodeUrl": dealer['qrcodeUrl'], "lbs": dev.get('lbs', False), "instructions": dev.get('instructions', ''), "logicalCode": dev.get('logicalCode', ''), "payAfterAd": Advertisement.objects(type = 'payAfter').count() > 0, "dealerId": dev['ownerId'], "dealerDes": dealer['description'], 'devNo': dev.get('devNo'), 'channelType': dev.channelType, 'countDown': False } action_box = ActionBtDeviceBuilder.create(dev['devType']['code'], dev) if action_box and action_box.support_count_down(): data.update({'countDown': True}) return JsonResponse({'result': 1, 'description': '', 'payload': data}) except Exception, e: logger.exception('unable to get equipmentPara, logicalCode = %s; error = %s' % (logicalCode, e)) return JsonErrorResponse(description = u'系统错误,请稍后再试') @error_tolerate(nil = DefaultJsonErrorResponse) @permission_required(ROLE.myuser) def getPackage(request): """ 设备套餐 :param request: :return: """ def cmp_by_price(x, y): if x['price'] < y['price']: return -1 elif x['price'] > y['price']: return 1 else: return 0 logicalCode = request.GET.get('logicalCode', None) if not logicalCode: return JsonResponse({'result': 0, 'description': u'获取数据失败,请重新扫码登录'}) device = Device.get_dev_by_logicalCode(logicalCode) if device is None: return JsonResponse({'result': 0, 'description': u'无此设备', 'payload': {}}) washConfig = device['washConfig'] group = Group.get_group(device['groupId']) # 探测是否地址为免费活动组,默认为否 is_free_service = group.get('isFree', False) appendix = u' 免费使用' if is_free_service else '' packages = [] for packageId, rule in washConfig.items(): packages.append( { 'id': packageId, 'coins': rule['coins'], 'name': rule['name'] + appendix, 'time': rule['time'], 'price': rule['price'], 'description': rule.get('description', ''), 'imgList': rule.get('imgList', []), 'unit': rule.get('unit', u'分钟') } ) packages.sort(cmp_by_price) return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': packages}) @permission_required(ROLE.myuser) def wxpayGateway(request): """ 用户请求充值 生成数据库充值订单,申请微信支付前台js签名 :param request: :return: """ currentUser = request.user try: logger.info('----%s unified order via %s begin----' % (repr(currentUser), PayAppType.WECHAT_MINI)) payload = json.loads(request.body) ruleId = str(payload.get('ruleId', 0)) isQuickPay = payload.get('quickPay', False) logicalCode = payload.get('logicalCode', 0) attachParas = payload.get('attachParas', {}) logger.info('attachParas = %s' % str(attachParas)) device = Device.get_dev_by_logicalCode(logicalCode) if not device: return JsonErrorResponse(description = u'该设备未注册,请换一台设备试试') payment_gateway = get_wechat_mini_env_pay_gateway(device) if not payment_gateway.enable: return JsonErrorResponse(description = u'不支持微信小程序') # 和其他用户流程类似, 生成一个GROUP相关用户 payload = { 'sex': currentUser.sex, 'city': currentUser.city, 'province': currentUser.province, 'country': currentUser.country, 'avatar': currentUser.avatar, 'nickname': currentUser.nickname, 'managerialOpenId': currentUser.openId, 'authAppId': currentUser.authAppId, 'agentId': currentUser.agentId } pay_user = MyUser.get_or_create(app_platform_type = AppPlatformType.WECHAT_MINI, open_id = currentUser.openId, group_id = device['groupId'], **payload) if isQuickPay: record = RechargeRecordBuilder.new_bt_quickpay( pay_user, device, ruleId, attachParas, payment_gateway) else: record = RechargeRecordBuilder.new_bt_recharge_record( pay_user, device, ruleId, attachParas, payment_gateway) if payment_gateway.pay_app_type == PayAppType.WECHAT: data = payment_gateway.generate_js_payment_params( payOpenId = pay_user.openId, out_trade_no = record.orderNo, notify_url = PAY_NOTIFY_URL.WECHAT_PAY_BACK, money = record.money, body = record.subject, attach = {'dealerId': record.ownerId}) OrderCacheMgr(record).initial() task_caller('poll_user_recharge_record', delay = PollRecordDefine.DELAY_BEFORE, expires = PollRecordDefine.TASK_EXPIRES, pay_app_type = PayAppType.WECHAT_MINI, record_id = str(record.id), interval = PollRecordDefine.WAIT_EACH_ROUND, total_count = PollRecordDefine.TOTAL_ROUNDS) data.update({'orderId': str(record.id)}) return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': data}) if payment_gateway.pay_app_type == PayAppType.JD_AGGR: open_id = currentUser.openId data = payment_gateway.unified_order( out_trade_no = record.orderNo, money = record.money, notify_url = PAY_NOTIFY_URL.JD_AGGRE_PAY_BACK, subject = record.subject, expire = 300, attach = {'dealerId': record.ownerId}, openId = open_id, gatewayMethod = GatewayMethod.MINIPROGRAM, **{'imei': payload.get('devNo', '5435435')}) logger.debug('jd unified order pay info = {}'.format(str(data))) # 启动轮询 OrderCacheMgr(record).initial() pi_type = payment_gateway.pi_type if pi_type == PiType.WX: data.update({'orderId': str(record.id), 'golden': True}) response = JsonOkResponse(payload = data) else: return JsonErrorResponse(u'调起支付失败,请刷新后重试') task_caller('poll_user_recharge_record', delay = PollRecordDefine.DELAY_BEFORE, expires = PollRecordDefine.TASK_EXPIRES, pay_app_type = PayAppType.JD_AGGR, record_id = str(record.id), interval = PollRecordDefine.WAIT_EACH_ROUND, total_count = PollRecordDefine.TOTAL_ROUNDS) return response return JsonErrorResponse(description = u"不支持的支付方式") except Exception, e: logger.exception('unable to request wechat payment order, error = %s' % (e.message,)) return JsonErrorResponse(description = u"系统错误,请重试") @permission_required(ROLE.myuser) def getBalanceList(request): """ 用户账户详情 :param request: :return: """ pageIndex = int(request.GET.get('pageIndex', 1)) pageSize = int(request.GET.get('pageSize', 10)) totalBalance, total, dataList = request.user.filter_my_balance() cmp_dealer = lambda x, y: 1 if x['dealerId'] > y['dealerId'] else -1 dataList.sort(cmp = cmp_dealer) return JsonResponse({'result': 1, 'description': '', 'payload': { 'totalBalance': totalBalance, 'total': total, 'dataList': dataList[(pageIndex - 1) * pageSize:pageIndex * pageSize]}}) @permission_required(ROLE.myuser) def asynDiscountList(request): """ 用户充值菜单 :param request: :return: """ logicalCode = request.GET.get('logicalCode', None) dev = Device.get_dev_by_logicalCode(logicalCode) if not dev: return JsonErrorResponse(description = u'系统错误,请稍后再试') group = Group.get_group(dev['groupId']) if group is None: return JsonErrorResponse(description = u'设备没有登记注册,暂时无法使用网络支付') ruleDict = group['ruleDict'] data = [{"adOrgId": None, "adClientId": None, "isactive": None, "created": None, "updated": None, "updateby": None, "id": ruleId, "payAmount": float(ruleId), "coins": float(coins), "equipmentId": None, "groupId": None, "createby": None } for ruleId, coins in ruleDict.items()] return JsonResponse({'result': 1, 'description': 'SUCCESS', 'payload': data}) @permission_required(ROLE.myuser) def getChargeRecord(request): pageIndex = int(request.GET.get('pageIndex', 1)) pageSize = int(request.GET.get('pageSize', 10)) openId = request.user.openId if not openId: return JsonResponse({"payload": {}}, status = 401) startTime = request.GET.get('startTime', Const.QUERY_START_DATE) endTime = request.GET.get('endTime', datetime.datetime.now().strftime('%Y-%m-%d')) records = ClientRechargeModelProxy.get_data_list( ownerId__in = MyUser.get_dealer_ids(openId = request.user.openId, productAgentId = request.user.productAgentId), startTime = startTime, endTime = endTime, openId = openId, result = RechargeRecord.PayResult.SUCCESS, via = 'recharge', hint = [('openId', 1)]) # type: QuerySetProxy if not records: return [] total = records.count() dataList = [] for record in records.paginate(pageIndex, pageSize): # type: RechargeRecord dataList.append({ 'time': record.to_datetime_str(record.dateTimeAdded), # 兼容 'createdTime': record.to_datetime_str(record.dateTimeAdded), 'order': record.wxOrderNo, 'amount': record.money, 'coins': record.coins, 'groupName': record.groupName, 'groupNumber': record.groupNumber, 'address': record.address, 'name': record.groupName, 'value': record.logicalCode, 'devTypeName': record.dev_type_name, 'equipmentTypeName': record.dev_type_name, # 兼容 'typeText': 'recharge', # 兼容 'via': record.via }) return JsonResponse( { 'result': 1, 'description': '', 'payload': { 'total': total, 'dataList': dataList } }) @error_tolerate(nil = DefaultJsonErrorResponse) @permission_required(ROLE.myuser) def verifyPayment(request): payload = json.loads(request.body) logicalCode = payload['logicalCode'] packageId = payload['packageId'] dev = Device.get_dev_by_logicalCode(logicalCode) # type: DeviceDict if dev is None: logger.error('not find device. logicalCode = %s' % logicalCode) return JsonErrorResponse(description = u'获取数据失败,请刷新后再试') pay_count = VirtualCoin(dev['washConfig'].get(packageId)['coins']) user = None if detect_wechat_client(request): user = request.user.clone_by_device_for_bt(AppPlatformType.WECHAT_MINI, dev) elif detect_alipay_client(request): user = request.user if not user: return JsonErrorResponse(description = u'无此用户,请登录') if Redpack.can_use(dealer=dev.owner, devTypeCode=dev.devTypeCode): package = dev['washConfig'].get(packageId) balance = user.calc_currency_balance(dev.owner, dev.group) # 正常套餐 红包 + 余额 如果他们能支撑套餐 直接启动 redpack = Redpack.auto_suit_with_coins({'openId': user.openId}, balance, package) if redpack: redpackCoins = Redpack.pre_deducted_coins(redpack['id'], package) pay_count -= redpackCoins errCode, errMsg = user.verify_payment_for_bt(dev, pay_count) return JsonResponse( { 'result': errCode, 'description': '', 'payload': { 'errMsg': errMsg } } ) @error_tolerate(nil = DefaultJsonErrorResponse) @permission_required(ROLE.myuser) def startAction(request): payload = json.loads(request.body) logicalCode = payload['logicalCode'] packageId = payload['packageId'] attachParas = payload.get('attachParas', {}) major = int(payload.get('major', 0x1)) minor = int(payload.get('minor', 0x1)) dev = Device.get_dev_by_logicalCode(logicalCode) # type: DeviceDict if dev is None: logger.error('not find device. logicalCode = %s' % logicalCode) return JsonErrorResponse(description = u'获取数据失败,请刷新后再试') dev['code'] = Const.BT_DEVICE_TYPE_CODE_MAP[int(payload.get('code', 1))] dev['major'] = major dev['minor'] = minor user = None if detect_wechat_client(request): user = request.user.clone_by_device_for_bt(AppPlatformType.WECHAT_MINI, dev) elif detect_alipay_client(request): user = request.user return _start_action(user, dev, packageId, attachParas) @permission_required(ROLE.myuser) def finishAction(request): try: payload = json.loads(request.body) logicalCode = payload['logicalCode'] packageId = payload['packageId'] orderId = str(payload['orderId']) cmdId = int(payload['cmdId']) major = payload['major'] minor = payload['minor'] code = int(payload['code']) errCode = int(payload['errCode']) dev = Device.get_dev_by_logicalCode(logicalCode) # type: DeviceDict if dev is None: logger.error('not find device. logicalCode = %s' % logicalCode) return JsonErrorResponse(description = u'获取数据失败,请刷新后再试') dev['major'] = major dev['minor'] = minor dev['code'] = code if detect_wechat_client(request): user = MyUser.objects.get(openId = request.user.openId, groupId = dev['groupId']) # type: MyUser elif detect_alipay_client(request): user = request.user # type: MyUser else: return JsonErrorResponse(description=u'客户端不支持,请确认在微信或支付宝打开') return _end_action(user, dev, packageId, cmdId, orderId, errCode) except Exception, e: logger.exception(e) return JsonResponse({'result': 0, 'description': u'系统开小差了,请您重新试试吧', 'payload': {}}) def _end_action(user, dev, packageId, cmdId, orderId, errCode): # type: (MyUser, DeviceDict, str, str, str, str)->JsonResponse logger.info('user({}) packageId({}) on device(logicalCode={}) end action errCode = {}'.format( repr(user), packageId, dev['logicalCode'], errCode)) try: result = FinishService(user, dev, cmdId, orderId, packageId, errCode).start() return JsonResponse(result) except Exception, e: logger.exception(e) return JsonResponse({'result': 0, 'description': u'系统开小差了,请您重新试试吧', 'payload': {}}) def _start_action(user, dev, packageId, attachParas): logger.info( '%s using packageId(%s) on device(logicalCode=%s)' % ( repr(user), packageId, dev['logicalCode'])) try: result = StartService(user, dev, packageId, attachParas).start() return JsonResponse(result) except Exception, e: logger.exception(e) return JsonResponse({'result': 0, 'description': u'系统开小差了,请您重新试试吧', 'payload': {}}) @require_POST @permission_required(ROLE.myuser) def alipayGateway(request): pay_user = request.user # type: MyUser try: logger.info('----%s unified order via %s begin----' % (repr(pay_user), AppPlatformType.ALIPAY)) payload = json.loads(request.body) ruleId = payload.get('ruleId') logicalCode = payload.get('logicalCode') isQuickPay = payload.get('quickPay') attachParas = payload.get('attachParas', {}) device = Device.get_dev_by_logicalCode(logicalCode) # type: DeviceDict if not device: return JsonErrorResponse(description = u'设备没有注册,请换一台设备重试') group = device.group # type: Group if not group: return JsonErrorResponse(description = u'设备没有注册,请换一台设备重试') dealer = device.owner if not dealer: return JsonErrorResponse(description = u'设备没有注册,请换一台设备重试') # 蓝牙还不支持聚合支付,直接走支付宝 payment_gateway = get_alipay_env_pay_gateway( source = device, pay_app_type = PayAppType.ALIPAY) # type: AliPayGateway if not payment_gateway.enable: return JsonErrorResponse(description = u'支付宝支付未开通,请使用微信支付') attachParas['dealerId'] = device['ownerId'] attachParas['agentId'] = str(pay_user.agentId) if isQuickPay: record = RechargeRecordBuilder.new_bt_quickpay( pay_user, device, ruleId, attachParas, payment_gateway) else: record = RechargeRecordBuilder.new_bt_recharge_record( pay_user, device, ruleId, attachParas, payment_gateway) result = get_alipay_gateway_result(gateway = payment_gateway, out_trade_no = record.orderNo, money = record.money, subject = record.subject, buyer_id = record.openId, body = json.dumps({'dealerId': record.ownerId}), notify_url = PAY_NOTIFY_URL.ALI_PAY_BACK) if result['code'] == u'10000': # 启动轮询任务去获取订单状态 OrderCacheMgr(record).initial() task_caller('poll_user_recharge_record', delay = PollRecordDefine.DELAY_BEFORE, expires = PollRecordDefine.TASK_EXPIRES, pay_app_type = PayAppType.ALIPAY, record_id = str(record.id), interval = PollRecordDefine.WAIT_EACH_ROUND, total_count = PollRecordDefine.TOTAL_ROUNDS) return JsonResponse( {'result': 1, 'payload': {'orderId': str(record.id), 'tradeNO': result['trade_no']}}) else: record.fail(description = result['code']) return JsonErrorResponse(description = u'系统错误,请重试') except Exception as e: logger.exception(e) return JsonErrorResponse(description = u'系统错误,请重试') finally: logger.info('----%s unified order via %s ended----' % (repr(pay_user), AppPlatformType.ALIPAY)) @permission_required(ROLE.myuser) def userInfo(request): user = request.user # type: MyUser agentId = user.agentId payload = {'nickname': request.user.nickname, 'balance': user.total_balance, 'agentId': agentId, 'domain': settings.MY_DOMAIN, 'avatarUrl': user.avatar if user.avatar else settings.DEFAULT_AVATAR_URL} agent = Agent.objects(id = agentId).first() # type: Optional[Agent] if agent: payload['agentFeatures'] = agent.features return JsonResponse({'result': 1, 'description': '', 'payload': payload}) def h5gateway(request): response = MiniGatewayResponseRedirect(str(request.GET.get('redirect'))) token = str(request.GET.get('token')) response.set_cookie(key = settings.JWT_AUTH_DOMAIN_COOKIE_NAME, value = settings.SERVICE_DOMAIN.MINI_WECHAT, max_age = 3600 * 24 * 30, domain = settings.COOKIE_DOMAIN, secure = False, httponly = False) response.set_cookie(key = jwt_session_key(settings.SERVICE_DOMAIN.MINI_WECHAT), value = token, max_age = 3600 * 24 * 30, domain = settings.COOKIE_DOMAIN, secure = False, httponly = False) return response @permission_required(ROLE.myuser) def countDown(request): dev = Device.get_dev_by_logicalCode(request.GET.get('logicalCode')) if not dev: return JsonResponse({'result': 0, 'description': '设备不存在', 'payload': {}}) if 'code' not in dev['devType'] or not dev['ownerId']: return JsonResponse({'result': 0, 'description': '设备未注册', 'payload': {}}) smart_box = ActionBtDeviceBuilder.create(dev['devType']['code'], dev) if not smart_box: count_down = False else: count_down = smart_box.support_count_down() if not count_down: return JsonResponse({'result': 0, 'description': '不支持计时', 'payload': {}}) service_cache = Device.get_dev_control_cache(dev['devNo']) if not service_cache: return JsonResponse({'result': 1, 'description': '没有正在运行的业务', 'payload': {'totalTime': 0, 'leftTime': 0, 'countDown': True}}) else: if service_cache['openId'] != request.user.openId: return JsonResponse({'result': 1, 'description': '没有正在运行的业务', 'payload': {'totalTime': 0, 'leftTime': 0, 'countDown': True}}) left_time = service_cache['finishedTime'] - int(time.time()) if left_time < 0: left_time = 0 return JsonResponse({ 'result': 1, 'description': '', 'payload': { 'totalTime': service_cache['totalTime'], 'leftTime': left_time, 'countDown': True }}) @permission_required(ROLE.myuser) def stopCountDown(request): dev = Device.get_dev_by_logicalCode(request.GET.get('logicalCode')) if not dev: return JsonResponse({'result': 0, 'description': '设备不存在', 'payload': {}}) if 'code' not in dev['devType'] or not dev['ownerId']: return JsonResponse({'result': 0, 'description': '设备未注册', 'payload': {}}) service_info = Device.get_dev_control_cache(dev['devNo']) if not service_info or 'openId' not in service_info or service_info['openId'] != request.user.openId: return JsonResponse({ 'result': 1, 'description': '', 'payload': {}}) else: Device.invalid_device_control_cache(dev['devNo']) return JsonResponse({ 'result': 1, 'description': '', 'payload': {}})