Bläddra i källkod

Merge branch 'cloud_storage_dev' into lang

# Conflicts:
#	Ansjer/urls.py
lang 3 år sedan
förälder
incheckning
8a927ef53a
5 ändrade filer med 292 tillägg och 9 borttagningar
  1. 1 1
      Ansjer/urls.py
  2. 19 3
      Controller/CloudStorage.py
  3. 249 0
      Controller/PaymentCycle.py
  4. 21 5
      Model/models.py
  5. 2 0
      Object/ResponseObject.py

+ 1 - 1
Ansjer/urls.py

@@ -18,7 +18,7 @@ from Controller import FeedBack, EquipmentOTA, EquipmentInfo, AdminManage, AppIn
     VerifyCodeController, FileController, UIDController, LogController, SalesController, \
     OrderTaskController, HistoryUIDController, UIDManageUserController, SerialNumberController, CompanyController, \
     RegionController, VPGController, LanguageController, TestController, DeviceConfirmRegion, S3GetStsController, \
-    DetectControllerV2, ShadowController, TestDetectController, PcInfo, PctestController, DeviceDebug, \
+    DetectControllerV2, ShadowController, TestDetectController, PcInfo, PctestController, DeviceDebug, PaymentCycle, \
     DeviceLogController
 from AdminController import UserManageController, RoleController, MenuController, TestServeController, \
     ServeManagementController, LogManagementController, DeviceManagementController, VersionManagementController

+ 19 - 3
Controller/CloudStorage.py

@@ -52,6 +52,7 @@ from Service.CommonService import CommonService
 from Object.m3u8generate import PlaylistGenerator
 from Object.WechatPayObject import WechatPayObject
 from django.db.models import Q, F, Count
+from Controller.PaymentCycle import Paypal
 from Ansjer.config import SERVER_TYPE
 from Service.ModelService import ModelService
 
@@ -216,7 +217,7 @@ class CloudStorageView(View):
         qs = qs.values("id", "title", "content", "price", "day", "currency", "bucket__storeDay",
                        "bucket__bucket", "bucket__area", "commodity_code",
                        "commodity_type", "is_discounts", "virtual_price", "expire",
-                       "discount_price", "discount_content", "symbol")
+                       "discount_price", "discount_content", "symbol","cycle_config_id")
 
         if qs.exists():
             ql = list(qs)
@@ -229,7 +230,8 @@ class CloudStorageView(View):
                 for key, val in enumerate(items_list):
                     pay_types = Pay_Type.objects.filter(store_meal=items_list[key]['id']).values("id", "payment")
                     items_list[key]['pay_type'] = list(pay_types)
-
+                    items_list[key]['is_pay_cycle'] = 1 if items_list[key]['cycle_config_id'] else 0
+                    del items_list[key]['cycle_config_id']
                 res_c = {'area': area, 'items': items_list}
                 res.append(res_c)
             #是否促销
@@ -1130,7 +1132,7 @@ class CloudStorageView(View):
         smqs = Store_Meal.objects.filter(id=rank, pay_type=pay_type, lang__lang=lang, is_show=0). \
             values('currency', 'price', 'lang__content', 'day',
                    'commodity_type', 'lang__title',
-                   'expire', 'commodity_code', 'discount_price', 'bucket__mold')
+                   'expire', 'commodity_code', 'discount_price', 'bucket__mold', 'cycle_config_id')
         if not smqs.exists():
             return response.json(173)
         currency = smqs[0]['currency']
@@ -1157,6 +1159,20 @@ class CloudStorageView(View):
 
         orderID = CommonService.createOrderID()
         if pay_type == 1:
+            # 订阅周期扣款
+            if(smqs[0]['cycle_config_id']):
+                subInfo = Paypal.subscriptions(store_info=smqs[0],lang=lang,orderID=orderID)
+                if not subInfo:
+                    return response.json(10048)
+                Order_Model.objects.create(orderID=orderID, UID=uid, channel=channel, userID_id=userID,
+                                           desc=content, payType=pay_type, payTime=nowTime,
+                                           price=price, currency=currency, addTime=nowTime, updTime=nowTime,
+                                           pay_url=subInfo['url'], isSelectDiscounts=is_select_discount,
+                                           commodity_code=commodity_code, commodity_type=commodity_type,
+                                           rank_id=rank, plan_id=subInfo['plan_id'])
+                return response.json(0, {"redirectUrl": subInfo['url'], "orderID": orderID})
+
+            #正常扣款
             cal_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
             if lang != 'cn':
                 cal_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)

+ 249 - 0
Controller/PaymentCycle.py

@@ -0,0 +1,249 @@
+from Ansjer.config import PAYPAL_CRD,SERVER_DOMAIN,SERVER_DOMAIN_SSL
+from Model.models import PayCycleConfigModel,Order_Model, Store_Meal, UID_Bucket, PromotionRuleModel, Unused_Uid_Meal,Device_Info
+from Service.CommonService import CommonService
+from django.http import JsonResponse, HttpResponseRedirect, HttpResponse
+import requests
+import time
+from Object.ResponseObject import ResponseObject
+import paypalrestsdk
+from paypalrestsdk import BillingAgreement
+from django.views.generic.base import View
+from django.db import transaction
+from Controller import CloudStorage
+from django.db.models import Q, F, Count
+
+#周期扣款相关
+class Paypal:
+    def subscriptions(store_info,lang,orderID):
+        cycle_config = PayCycleConfigModel.objects.filter(id=store_info['cycle_config_id']).values()
+        if not cycle_config:
+            return False
+        cal_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+        if lang != 'cn':
+            cal_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+        return_url = "{SERVER_DOMAIN_SSL}payCycle/paypalCycleReturn?lang={lang}". \
+            format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, lang=lang)
+        # call_sub_url = "http://binbin.uicp.vip/cloudstorage/dopaypalcallback?orderID={orderID}".format(
+        # SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, orderID=orderID)
+        BillingPlan = {
+            "description": orderID,
+            "merchant_preferences": {
+                "auto_bill_amount": "YES",
+                "cancel_url": cal_url,  # 取消协议url
+                "initial_fail_amount_action": "CANCEL",
+                "max_fail_attempts": "1",  # 允许的最大失败付款尝试次数
+                "return_url": return_url,  # 客户批准协议的url
+                # "notify_url": "http://www.notify.com",  #通知客户协议已创建的 URL。只读并保留供将来使用。
+                "setup_fee": {
+                    "currency": store_info['currency'],
+                    "value": store_info['price'],
+                }
+            },
+            "name": store_info['lang__content'],
+            "payment_definitions": [
+                {
+                    "amount": {
+                        "currency": store_info['currency'],
+                        "value": store_info['price']
+                    },
+                    # "charge_models": [
+                    #     {
+                    #         "amount": {
+                    #             "currency": "USD",
+                    #             "value": "20"
+                    #         },
+                    #         "type": "TAX"   #税金
+                    #     }
+                    # ],
+                    "cycles": cycle_config[0]['cycles'],
+                    "frequency": cycle_config[0]['frequency'],
+                    "frequency_interval": cycle_config[0]['frequencyInterval'],
+                    "name": store_info['lang__title'],
+                    "type": "REGULAR"
+                },
+            ],
+            "type": "INFINITE",
+        }
+        paypalrestsdk.configure(PAYPAL_CRD)
+        billing_plan = paypalrestsdk.BillingPlan(BillingPlan)
+        if billing_plan.create():
+            billing_plan.activate()  # 激活
+            plan_id = billing_plan.id
+        else:
+            print(billing_plan.error)
+            return False
+
+        now_time = int(time.time())
+        start_date_timestamp = CommonService.calcMonthLater(1, now_time) - (5 * 86400) #下次扣款为下个月提前5天扣款
+        start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
+        #订阅
+        billingAgreement = {
+            "name": store_info['lang__content'],
+            "description": orderID,
+            "start_date": start_date_str,
+            "plan": {
+                "id": plan_id
+            },
+            "payer": {
+                "payment_method": "paypal"
+            },
+        }
+        billing_agreement = paypalrestsdk.BillingAgreement(billingAgreement)
+        # print(billing_agreement.create())
+        if billing_agreement.create():
+            for link in billing_agreement.links:
+                if link.rel == "approval_url":
+                    return {"plan_id": plan_id, "url": link.href}
+        else:
+            print(billing_agreement.error)
+            return False
+
+class PaypalCycleNotify(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.GET, request, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.POST, request, operation)
+
+    def validation(self, request_dict, request, operation):
+        response = ResponseObject()
+        if operation is None:
+            return response.json(444, 'error path')
+        elif operation == 'paypalCycleReturn':  # paypal成功订阅回调
+            return self.do_paypal_cycle_return(request_dict, response)
+        elif operation == 'paypalCycleNotify':  # paypal 周期付款回调
+            return self.do_paypal_webhook_notify(request_dict, response)
+    def do_paypal_cycle_return(self, request_dict, response):
+        lang = request_dict.get('lang', 'en')
+        token = request_dict.get('token',None)
+        paypalrestsdk.configure(PAYPAL_CRD)
+        billing_agreement = paypalrestsdk.BillingAgreement()
+        billing_agreement_response = billing_agreement.execute(token)
+        if billing_agreement_response.error:
+            print(billing_agreement_response.error)
+            red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+            if lang != 'cn':
+                red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+            return HttpResponseRedirect(red_url)
+
+        orderID = billing_agreement_response.description
+        agreement_id = billing_agreement_response.id
+        promotion_rule_id = ''
+        try:
+            order_qs = Order_Model.objects.filter(orderID=orderID, status=0)
+
+            if not orderID:
+                print("not orderID")
+                red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+                if lang != 'cn':
+                    red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+                return HttpResponseRedirect(red_url)
+
+            order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",
+                                         "userID__userID",
+                                         "userID__username")
+            userid = order_list[0]['userID__userID']
+            username = order_list[0]['userID__username']
+            UID = order_list[0]['UID']
+            channel = order_list[0]['channel']
+            rank = order_list[0]['rank']
+            smqs = Store_Meal.objects.filter(id=rank). \
+                values("day", "bucket_id", "bucket__storeDay", "expire")
+            bucketId = smqs[0]['bucket_id']
+            if not smqs.exists():
+                print("not smqs")
+                red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+                if lang != 'cn':
+                    red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+                return HttpResponseRedirect(red_url)
+            # ##
+            ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay", "bucket__region",
+                                                             "endTime", "use_status")
+            expire = smqs[0]['expire']
+
+            if order_list[0]['isSelectDiscounts'] == 1:
+                expire = smqs[0]['expire'] * 2
+            # 是否有促销
+            nowTime = int(time.time())
+            promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
+                                                          endTime__gte=nowTime).values('id','ruleConfig')
+            if promotion.exists():
+                promotion_rule_id = promotion[0]['id']
+                expire = expire * 2
+            with transaction.atomic():
+                if ubqs.exists():
+                    ubq = ubqs[0]
+                    if ubq['use_status'] == 1 and ubq['bucket_id'] == bucketId:  #套餐使用中并且相同套餐叠加过期时间
+                        endTime = CommonService.calcMonthLater(expire, ubq['endTime'])
+                        UID_Bucket.objects.filter(id=ubq['id']).update \
+                            (uid=UID, channel=channel, bucket_id=bucketId,
+                             endTime=endTime, updateTime=nowTime)
+                    else:     #已过期或者不相同的套餐加入未使用的关联套餐表
+                        has_unused = Unused_Uid_Meal.objects.filter(uid=UID, bucket_id=bucketId).values("id")
+                        nums = 2 if order_list[0]['isSelectDiscounts'] == 1 else 1
+                        if promotion.exists():
+                            nums = nums + 1
+                        if has_unused.exists():
+                            Unused_Uid_Meal.objects.filter(id=has_unused[0]['id']).update(num=F('num') + nums)
+                        else:
+                            Unused_Uid_Meal.objects.create(uid=UID,channel=channel,addTime=nowTime,num=nums,
+                                                           expire=smqs[0]['expire'],bucket_id=bucketId)
+                        UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
+                    uid_bucket_id = ubq['id']
+                else:
+                    endTime = CommonService.calcMonthLater(expire)
+                    ub_cqs = UID_Bucket.objects.create \
+                        (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
+                         updateTime=nowTime,use_status=1)
+                    uid_bucket_id = ub_cqs.id
+
+                dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
+                if dvq.exists():
+                    dvq_set_update_dict = {
+                        'vodPrimaryUserID': userid,
+                        'vodPrimaryMaster': username
+                    }
+                    dvq.update(**dvq_set_update_dict)
+
+                # uid_main_exist = UIDMainUser.objects.filter(UID=UID)
+                # if not uid_main_exist.exists():
+                #     uid_main_dict = {
+                #         'UID': UID,
+                #         'user_id': userid
+                #     }
+                #     UIDMainUser.objects.create(**uid_main_dict)
+
+                order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id,
+                                promotion_rule_id=promotion_rule_id,agreement_id=agreement_id)
+                datetime = time.strftime("%Y-%m-%d", time.localtime())
+                sys_msg_text_list = ['温馨提示:尊敬的客户,您的' + UID + '设备在' + datetime + '已成功订阅云存套餐',
+                                     'Dear customer,you already subscribed the cloud storage package successfully for device ' + UID + ' on ' + time.strftime(
+                                         "%b %dth,%Y", time.localtime())]
+
+                CloudStorage.CloudStorageView.do_vod_msg_Notice(self, UID, channel, userid, lang, sys_msg_text_list, 'SMS_219738485')
+
+                # return response.json(0)
+                red_url = "{SERVER_DOMAIN_SSL}web/paid2/success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+                if lang != 'cn':
+                    red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+                return HttpResponseRedirect(red_url)
+        except Exception as e:
+            print(repr(e))
+            if order_qs:
+                order_qs.update(status=10, promotion_rule_id=promotion_rule_id)
+            red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+            if lang != 'cn':
+                red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
+            return HttpResponseRedirect(red_url)
+
+
+    def do_paypal_webhook_notify(self, request_dict, response):
+        paymentId = request_dict.get('paymentId', None)
+        PayerID = request_dict.get('PayerID', None)
+        lang = request_dict.get('lang', 'en')
+        token = request_dict.get('token',None)
+        paypalrestsdk.configure(PAYPAL_CRD)

+ 21 - 5
Model/models.py

@@ -472,9 +472,8 @@ class Store_Meal(models.Model):
     is_show = models.SmallIntegerField(default=0, verbose_name=u'该套餐是否隐藏 [0=否,1是]')
     # lang = models.CharField(default='', max_length=20, verbose_name='语言/国家')
     lang = models.ManyToManyField(to='Lang', verbose_name='套餐语言', db_table='store_meal_lang')
-
+    cycle_config_id = models.IntegerField(null=True, verbose_name='周期付款配置表id')
     # 备用字段
-    spare_1 = models.CharField(default='', blank=True, max_length=64, verbose_name=u'备用字段1')
     spare_2 = models.CharField(default='', blank=True, max_length=64, verbose_name=u'备用字段2')
     spare_3 = models.CharField(default='', blank=True, max_length=64, db_index=True, verbose_name=u'备用字段3')
     spare_4 = models.CharField(default='', blank=True, max_length=64, db_index=True, verbose_name=u'备用字段4')
@@ -623,12 +622,12 @@ class Order_Model(models.Model):
     pay_url = models.CharField(max_length=2000, default='', verbose_name='支付url')
     paypal = models.CharField(max_length=500, null=True, blank=True, verbose_name='支付批准url')
     promotion_rule_id = models.CharField(blank=True, max_length=64, default='', verbose_name='促销id')
+    plan_id = models.CharField(default='', blank=True, max_length=64, verbose_name=u'paypal计划id')
+    agreement_id = models.CharField(default='', blank=True, max_length=64, verbose_name=u'paypal协议id')
 
     # 备用字段
-    spare_1 = models.CharField(default='', blank=True, max_length=64, verbose_name=u'备用字段1')
-    spare_2 = models.CharField(default='', blank=True, max_length=64, verbose_name=u'备用字段2')
-    spare_3 = models.CharField(default='', blank=True, max_length=64, db_index=True, verbose_name=u'备用字段3')
     spare_4 = models.CharField(default='', blank=True, max_length=64, db_index=True, verbose_name=u'备用字段4')
+    spare_2 = models.CharField(default='', blank=True, max_length=64, verbose_name=u'备用字段2')
 
     def __str__(self):
         return self.orderID
@@ -639,6 +638,23 @@ class Order_Model(models.Model):
         verbose_name_plural = verbose_name
         ordering = ('-orderID',)
 
+class PayCycleConfigModel(models.Model):
+    id = models.AutoField(primary_key=True, verbose_name='主键')
+    # name = models.CharField(default='',max_length=200, verbose_name='计划名字')
+    # tax = models.CharField(max_length=2000, default='', verbose_name='税金')
+    cycles = models.IntegerField(verbose_name='周期:0代表无限期', default=0)
+    frequency = models.CharField(max_length=50,verbose_name='频率,MONTH,WEEK,YEAR', default=0)
+    frequencyInterval = models.IntegerField(default=0, verbose_name='频率周期')
+    remark = models.CharField(max_length=1000, default='', verbose_name='备注')
+
+    def __str__(self):
+        return self.id
+
+    class Meta:
+        db_table = 'pay_cycle_config'
+        verbose_name = u'周期付款计划'
+        verbose_name_plural = verbose_name
+
 class PromotionRuleModel(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='主键')
     ruleName = models.TextField(default='', verbose_name='规则名字')                     #json格式, 例: {"cn":"黑色星期五","en":"Black Friday"}

+ 2 - 0
Object/ResponseObject.py

@@ -104,6 +104,7 @@ class ResponseObject(object):
             10045: 'Already the latest version',
             10046: 'Sorry, users who have activated cloud storage packages do not support logout at the moment, please contact customer service',
             10047: 'Please delete all devices under your account first',
+            10048: 'Subscribe to the failure',
         }
         data_cn = {
             0: '成功',
@@ -201,6 +202,7 @@ class ResponseObject(object):
             10045: '当前为最新版本',
             10046: '已开通云存的用户,暂不支持注销,请联系客服',
             10047: '请先删除您当前帐户下的所有设备',
+            10048: '订阅失败',
         }
         if self.lang == 'cn':
             msg = data_cn