| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869 | import datetime as date_timeimport jsonimport loggingimport timeimport tracebackimport paypalrestsdkfrom django.db import transactionfrom django.db.models import Q, Ffrom django.http import HttpResponseRedirect, HttpResponsefrom django.views.generic.base import Viewfrom Ansjer.config import PAYPAL_CRD, SERVER_DOMAIN_SSL, PAYPAL_WEB_HOOK_ID, CONFIG_INFO, \    CONFIG_US, CONFIG_EURfrom Controller import CloudStoragefrom Model.models import PayCycleConfigModel, Store_Meal, UID_Bucket, PromotionRuleModel, \    Unused_Uid_Meal, Device_Info, CouponModel, Order_Model, PaypalWebHookEvent, CountryModel, AiServicefrom Object.ResponseObject import ResponseObjectfrom Object.TokenObject import TokenObjectfrom Service.CommonService import CommonServicePAY_LOGGER = logging.getLogger('pay')# 周期扣款相关class Paypal:    # 检查是否有重复订阅    def checkSubscriptions(userID, uid, rank, app_type):        hasOrder = Order_Model.objects.filter(UID=uid, app_type=app_type)        hasOrder = hasOrder.filter(~Q(agreement_id='')).values('agreement_id', 'orderID').order_by('-addTime')[0:1]        if not hasOrder.exists():            return True        if app_type == 1:            paypalrestsdk.configure(PAYPAL_CRD['Zosi'])        elif app_type == 2:            paypalrestsdk.configure(PAYPAL_CRD['Vsees'])        billing_agreement = paypalrestsdk.BillingAgreement.find(hasOrder[0]['agreement_id'])        if billing_agreement.state == 'Active':            return False        return True    def subscriptions(store_info, lang, orderID, price, app_type):        logger = logging.getLogger('pay')        cycle_config = PayCycleConfigModel.objects.filter(id=store_info['cycle_config_id']).values()        if not cycle_config:            logger.info('----创建订阅失败----')            logger.info('订阅配置失败')            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}&app_type={app_type}". \            format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, lang=lang, app_type=app_type)        # call_sub_url = "http://binbin.uicp.vip/cloudstorage/dopaypalcallback?orderID={orderID}".format(        # SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, orderID=orderID)        # exit(price)        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": 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",        }        if app_type == 1:            paypalrestsdk.configure(PAYPAL_CRD['Zosi'])        elif app_type == 2:            paypalrestsdk.configure(PAYPAL_CRD['Vsees'])        billing_plan = paypalrestsdk.BillingPlan(BillingPlan)        if billing_plan.create():            billing_plan.activate()  # 激活            plan_id = billing_plan.id        else:            logger.info('----创建计划失败----')            logger.info(billing_plan.error)            return False        now_time = int(time.time())        if cycle_config[0]['frequency'] == "DAY":            start_date_timestamp = now_time + 86400 - 3600  # 下次扣款为明天,提前1个小时扣款            start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")        elif cycle_config[0]['frequency'] == "MONTH":            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:            logger.info('----创建订阅失败----')            logger.info(billing_agreement.error)            return Falseclass 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, request, response)        elif operation == 'subscriptionBreakNotify':  # paypal 订阅相关回调            return self.do_subscription_break_notify(request_dict, request, response)    def create_vod(self, uid, expire, is_ai, now_time, channel, bucket_id, order_id, userid, username):        ubqs = UID_Bucket.objects.filter(uid=uid).values("id", "endTime", "use_status")        end_time = CommonService.calcMonthLater(expire)        use_flag = True        with transaction.atomic():            if ubqs.exists():                ubq = ubqs[0]                uid_bucket_id = ubq['id']                if ubq['use_status'] == 1 and ubq['endTime'] > now_time:  # 套餐使用中并且未过期,加入未使用的关联套餐表                    Unused_Uid_Meal.objects.create(uid=uid, channel=channel, addTime=now_time, is_ai=is_ai,                                                   order_id=order_id, expire=expire, bucket_id=bucket_id)                    UID_Bucket.objects.filter(id=uid_bucket_id).update(has_unused=1)                    use_flag = False                else:  # 云存服务已过期则重新开通云存服务                    UID_Bucket.objects.filter(id=uid_bucket_id).update(uid=uid, channel=channel,                                                                       bucket_id=bucket_id,                                                                       endTime=end_time,                                                                       updateTime=now_time,                                                                       use_status=1,                                                                       orderId=order_id)            else:                ub_cqs = UID_Bucket.objects.create \                    (uid=uid, channel=channel, bucket_id=bucket_id, endTime=end_time, addTime=now_time,                     updateTime=now_time, use_status=1, orderId=order_id)                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)            # 开通AI服务            if is_ai and use_flag:                ai_service = AiService.objects.filter(uid=uid, channel=channel)                if ai_service.exists():  # 有正在使用的套餐,套餐结束时间保存为套餐有效期                    ai_service.update(updTime=now_time, use_status=1, orders_id=order_id, endTime=end_time)                else:                    AiService.objects.create(uid=uid, channel=channel, detect_status=1, addTime=now_time,                                             updTime=now_time, endTime=end_time, use_status=1, orders_id=order_id)            return uid_bucket_id    def do_paypal_cycle_return(self, request_dict, response):        lang = request_dict.get('lang', 'en')        token = request_dict.get('token', None)        app_type = int(request_dict.get('app_type', 1))        logger = logging.getLogger('pay')        logger.info('--------进入paypay首次订阅付款回调--------')        logger.info(request_dict)        if app_type == 1:            paypalrestsdk.configure(PAYPAL_CRD['Zosi'])        elif app_type == 2:            paypalrestsdk.configure(PAYPAL_CRD['Vsees'])        billing_agreement = paypalrestsdk.BillingAgreement()        billing_agreement_response = billing_agreement.execute(token)        if billing_agreement_response.error:            logger.info('----付款失败----')            logger.info(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        state = billing_agreement_response.state        nowTime = int(time.time())        promotion_rule_id = ''        logger.info('----订阅详情----')        logger.info(billing_agreement_response)        agreement_id = billing_agreement_response.id        order_qs = Order_Model.objects.filter(orderID=orderID, status=0)        order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",                                     "userID__userID",                                     "userID__username", 'coupon_id')        if not orderID:            logger.info('----订阅订单号失效----')            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)        UID = order_list[0]['UID']        if state != 'Active':            order_qs.update(status=2, promotion_rule_id=promotion_rule_id)            logger.info('----UID:{UID},用户名:{last_time} {first_time}首次订阅付款失败----'.format                        (UID=UID,                         last_time=billing_agreement_response.payer.payer_info.last_name,                         first_time=billing_agreement_response.payer.payer_info.first_time,                         ))            logger.info('billing_agreement_state')            logger.info(state)            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)        try:            userid = order_list[0]['userID__userID']            username = order_list[0]['userID__username']            channel = order_list[0]['channel']            rank = order_list[0]['rank']            smqs = Store_Meal.objects.filter(id=rank).values("day", "bucket_id", "bucket__storeDay", "expire", "is_ai")            bucketId = smqs[0]['bucket_id']            if not smqs.exists():                logger.info('----订阅套餐失效----')                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']            is_ai = smqs[0]['is_ai']            if order_list[0]['isSelectDiscounts'] == 1:                expire = smqs[0]['expire'] * 2            # 是否有促销            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            endTime = CommonService.calcMonthLater(expire)            use_flag = True            with transaction.atomic():                if ubqs.exists():                    ubq = ubqs[0]                    uid_bucket_id = ubq['id']                    if ubq['use_status'] == 1 and ubq['endTime'] > nowTime:  # 套餐使用中并且未过期,加入未使用的关联套餐表                        Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, is_ai=is_ai,                                                       order_id=orderID, expire=expire, bucket_id=bucketId)                        update_status = UID_Bucket.objects.filter(id=uid_bucket_id).update(has_unused=1)                        use_flag = False                    else:  # 云存服务已过期则重新开通云存服务                        update_status = UID_Bucket.objects.filter(id=uid_bucket_id).update(uid=UID, channel=channel,                                                                                           bucket_id=bucketId,                                                                                           endTime=endTime,                                                                                           updateTime=nowTime,                                                                                           use_status=1,                                                                                           orderId=orderID)                else:                    ub_cqs = UID_Bucket.objects.create \                        (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,                         updateTime=nowTime, use_status=1, orderId=orderID)                    update_status = True                    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)                # 开通AI服务                if is_ai and use_flag:                    ai_service = AiService.objects.filter(uid=UID, channel=channel)                    if ai_service.exists():  # 有正在使用的套餐,套餐结束时间保存为套餐有效期                        ai_service.update(updTime=nowTime, use_status=1, orders_id=orderID, endTime=endTime)                    else:                        AiService.objects.create(uid=UID, channel=channel, detect_status=1, addTime=nowTime,                                                 updTime=nowTime, endTime=endTime, use_status=1, orders_id=orderID)                # 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)                # 核销coupon                if order_list[0]['coupon_id']:                    CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)                logger.info(                    'uid:{},uid_bucket_id:{},update_status:{},order_id:{}'.format(UID, uid_bucket_id, update_status,                                                                                  orderID))                order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id, create_vod=1, payTime=nowTime,                                promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)                # 如果存在序列号,消息提示用序列号                device_name = CommonService.query_serial_with_uid(uid=UID)                datetime = time.strftime("%Y-%m-%d", time.localtime())                sys_msg_text_list = [                    '温馨提示:尊敬的客户,您的' + device_name + '设备在' + datetime + '已成功订阅云存套餐',                    'Dear customer, you have now successfully subscribed to the cloud storage plan for device ' + device_name + ' on ' + time.strftime(                        "%b %dth,%Y", time.localtime())]                CloudStorage.CloudStorageView().do_vod_msg_notice(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)                logger.info('{UID}成功开通paypal自动续费:----'.format(UID=UID))                return HttpResponseRedirect(red_url)        except Exception as e:            print(repr(e))            logger.info('do_paypal_cycle_return支付失败:----')            logger.info('{UID}开通paypal自动续费失败'.format(UID=UID))            logger.info("错误行数:{errLine}".format(errLine=e.__traceback__.tb_lineno))            logger.info(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, request, response):        PAY_LOGGER.info('--------进入周期扣款钩子--------')        if not request.body:            PAY_LOGGER.info('PayPal周期扣款失败---缺失请求体')            return HttpResponse('fail', status=500)        json_agreement_str = request.body.decode("utf-8")        json_obj = json.loads(json_agreement_str)        header = request.META        paypal_body = json_obj.get('resource')        PAY_LOGGER.info('----请求体数据:{}----'.format(json_agreement_str))        PAY_LOGGER.info('----请求头数据:{}----'.format(header))        try:            transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID', None)            transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME', None)            cert_url = header.get('HTTP_PAYPAL_CERT_URL', None)            transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG', None)            auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO', None)            event_type = json_obj.get('event_type')            summary = json_obj.get('summary')            resource_type = json_obj.get('resource_type')            agreement_id = paypal_body.get('billing_agreement_id')            paypal_transaction_id = paypal_body.get('id')            amount = paypal_body.get('amount')            fee = paypal_body['transaction_fee']['value'] if 'transaction_fee' in paypal_body else 0            PaypalWebHookEventInsert = {                'webhook_event_id': json_obj.get('id'),                'resource_type': json_obj.get('resource_type'),                'event_type': 1,                'summary': summary,                'trade_no': paypal_transaction_id,                'resource': json_agreement_str,                'created_time': int(time.time()),            }            if event_type != 'PAYMENT.SALE.COMPLETED':                PAY_LOGGER.info('----event_type异常:{}----'.format(event_type))            if resource_type == 'sale' and paypal_body.get('state') == 'completed':                paypalrestsdk.configure(PAYPAL_CRD['Zosi'])                response = paypalrestsdk.WebhookEvent.verify(                    transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID['Zosi']['paypalCycleNotify'],                    json_agreement_str, cert_url, transmission_sig, auth_algo)                app_type = 1                if not response:                    paypalrestsdk.configure(PAYPAL_CRD['Vsees'])                    response = paypalrestsdk.WebhookEvent.verify(                        transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID['Vsees']['paypalCycleNotify'],                        json_agreement_str, cert_url, transmission_sig, auth_algo)                    if not response:                        PAY_LOGGER.info('PayPal周期扣款失败---签名验证失败')                        return HttpResponse('Fail', status=500)                    app_type = 2            else:                PAY_LOGGER.info('PayPal周期扣款失败,付款状态有误,resource_type:{},state:{}----'.                                format(resource_type, paypal_body.get('state')))                return HttpResponse('Fail', status=500)            nowTime = int(time.time())            if not agreement_id:                # 记录钩子日志                PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)                # 普通支付,更新paypal交易id                paymentID = paypal_body.get('parent_payment')                if paymentID and paypal_transaction_id:                    # 查询客户地区信息,地区跟服务器配置不匹配,返回500                    order_qs = Order_Model.objects.filter(paymentID=paymentID).values('UID', 'userID__region_country',                                                                                      'create_vod', 'orderID',                                                                                      'channel', 'rank__is_ai',                                                                                      'rank__expire',                                                                                      'rank__bucket__id',                                                                                      'isSelectDiscounts',                                                                                      'userID__username',                                                                                      'userID__userID', 'coupon_id')                    if not order_qs.exists():                        PAY_LOGGER.info('PayPal周期扣款失败---根据paymentID查询订单数据不存在')                        return HttpResponse('Fail', status=500)                    # 判断用户地区是否跟服务器地区匹配                    uid = order_qs[0]['UID']                    country_id = order_qs[0]['userID__region_country']                    if not self.config_match_region(uid, country_id):                        return HttpResponse('Fail', status=500)                    if order_qs[0]['create_vod']:                        order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime, fee=fee)                    else:                        # 是否有促销                        expire = order_qs[0]['rank__expire']                        isSelectDiscounts = order_qs[0]['isSelectDiscounts']                        is_ai = order_qs[0]['rank__is_ai']                        channel = order_qs[0]['channel']                        bucketId = order_qs[0]['rank__bucket__id']                        userid = order_qs[0]['userID__userID']                        username = order_qs[0]['userID__username']                        order_id = order_qs[0]['orderID']                        coupon_id = order_qs[0]['coupon_id']                        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                        else:                            promotion_rule_id = ''                        if isSelectDiscounts:                            expire = expire * 2                        uid_bucket_id = self.create_vod(uid, expire, is_ai, nowTime, channel, bucketId, order_id,                                                        userid, username)                        order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime,                                        uid_bucket_id=uid_bucket_id, create_vod=1, fee=fee,                                        promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)                        # 核销coupon                        if coupon_id:                            CouponModel.objects.filter(id=coupon_id).update(use_status=2, update_time=nowTime)                    PAY_LOGGER.info('PayPal周期扣款成功---更新交易id:{}'.format(paypal_transaction_id))                    return HttpResponse('success')                else:                    PAY_LOGGER.info('PayPal周期扣款失败---paymentID:{}或paypal_transaction_id:{}为空'.                                    format(paymentID, paypal_transaction_id))                    return HttpResponse('fail', status=500)            billing_agreement = paypalrestsdk.BillingAgreement.find(agreement_id)            PAY_LOGGER.info('billing_agreement:{}'.format(billing_agreement))            # 记录钩子日志            PaypalWebHookEventInsert['agreement_desc'] = repr(billing_agreement)            PaypalWebHookEventInsert['agreement_id'] = agreement_id            PaypalWebHookEventInsert['orderID'] = billing_agreement.description            paypal_webhook_event_qs = PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)            # 查询订单数据            order_id = billing_agreement.description            order_qs = Order_Model.objects.filter(orderID=order_id).values('UID', 'channel', 'commodity_code', 'rank',                                                                           'isSelectDiscounts', 'plan_id', 'desc',                                                                           'payType', 'currency', 'addTime',                                                                           'commodity_type', 'updTime', 'order_type',                                                                           'userID__userID', 'uid_bucket_id',                                                                           'userID__username', 'userID__region_country',                                                                           'create_vod', 'coupon_id')            if not order_qs.exists():                PAY_LOGGER.info('PayPal周期扣款失败---根据order_id查询订单数据不存在')                return HttpResponse('fail', status=500)            # 判断用户地区是否跟服务器地区匹配            uid = order_qs[0]['UID']            country_id = order_qs[0]['userID__region_country']            if not self.config_match_region(uid, country_id):                return HttpResponse('Fail', status=500)            UID = order_qs[0]['UID']            isSelectDiscounts = order_qs[0]['isSelectDiscounts']            coupon_id = order_qs[0]['coupon_id']            userid = order_qs[0]['userID__userID']            username = order_qs[0]['userID__username']            channel = order_qs[0]['channel']            rank = order_qs[0]['rank']            store_meal_qs = Store_Meal.objects.filter(id=rank).values("bucket_id", "expire", "is_ai")            if not store_meal_qs.exists():                PAY_LOGGER.info('{} PayPal周期扣款失败---套餐数据不存在'.format(UID))                return HttpResponse('fail', status=500)            expire = store_meal_qs[0]['expire']            is_ai = store_meal_qs[0]['is_ai']            bucketId = store_meal_qs[0]['bucket_id']            # PayPal周期扣款首次扣款            if billing_agreement.agreement_details.cycles_completed == '0':                if order_qs[0]['create_vod']:                    order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime, fee=fee)                else:                    # 是否有促销                    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                    else:                        promotion_rule_id = ''                    if isSelectDiscounts:                        expire = expire * 2                    uid_bucket_id = self.create_vod(UID, expire, is_ai, nowTime, channel, bucketId, order_id,                                                    userid, username)                    order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime,                                    uid_bucket_id=uid_bucket_id, create_vod=1, fee=fee,                                    promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)                    # 核销coupon                    if coupon_id:                        CouponModel.objects.filter(id=coupon_id).update(use_status=2, update_time=nowTime)                PAY_LOGGER.info('{} PayPal周期扣款首次扣款成功'.format(UID))                return HttpResponse('success')            if order_qs[0]['addTime'] + 9200 > nowTime:  # 避免续费订单重复支付                PAY_LOGGER.info('{} PayPal周期扣款失败---续费订单已创建'.format(UID))                return HttpResponse('success')            desc = order_qs[0]['desc']            pay_type = order_qs[0]['payType']            commodity_code = order_qs[0]['commodity_code']            commodity_type = order_qs[0]['commodity_type']            plan_id = order_qs[0]['plan_id']            order_type = order_qs[0]['order_type']            endTime = CommonService.calcMonthLater(expire)            use_flag = True            orderID = CommonService.createOrderID()            ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "endTime", "use_status")            with transaction.atomic():                if ubqs.exists():                    ubq = ubqs[0]                    uid_bucket_id = ubq['id']                    if ubq['use_status'] == 1 and ubq['endTime'] > nowTime:  # 套餐使用中并且未过期                        Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, is_ai=is_ai,                                                       order_id=orderID, expire=expire, bucket_id=bucketId)                        update_status = UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)                        use_flag = False                    else:  # 已过期或者不相同的套餐加入未使用的关联套餐表                        update_status = UID_Bucket.objects.filter(id=uid_bucket_id).update(uid=UID, channel=channel,                                                                                           bucket_id=bucketId,                                                                                           endTime=endTime,                                                                                           updateTime=nowTime,                                                                                           use_status=1,                                                                                           orderId=orderID)                else:                    ub_cqs = UID_Bucket.objects.create(uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime,                                                       addTime=nowTime, updateTime=nowTime, use_status=1,                                                       orderId=orderID)                    uid_bucket_id = ub_cqs.id                    update_status = True                PAY_LOGGER.info(                    'uid:{},uid_bucket_id:{},update_status:{},order_id:{}'.format(UID, uid_bucket_id, update_status,                                                                                  orderID))                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)                store_meal_qs = Store_Meal.objects.filter(id=rank, lang__lang='cn', is_show=0).values('lang__title',                                                                                                      'lang__content')                if store_meal_qs.exists():                    store_meal_name = store_meal_qs[0]['lang__title'] + '-' + store_meal_qs[0]['lang__content']                else:                    store_meal_name = '未知套餐'                Order_Model.objects.create(orderID=orderID, UID=UID, channel=channel, userID_id=userid,                                           desc=desc, payType=pay_type, payTime=nowTime,                                           price=amount.get('total'), app_type=app_type,                                           currency=order_qs[0]['currency'], addTime=nowTime,                                           updTime=nowTime, order_type=order_type,                                           pay_url='', isSelectDiscounts=0, fee=fee,                                           commodity_code=commodity_code, create_vod=1,                                           commodity_type=commodity_type, rank_id=rank, paymentID='',                                           coupon_id='', uid_bucket_id=uid_bucket_id, status=1,                                           agreement_id=agreement_id, store_meal_name=store_meal_name,                                           plan_id=plan_id, ai_rank_id=1, trade_no=paypal_transaction_id)                # 开通AI服务                if is_ai and use_flag:                    ai_service = AiService.objects.filter(uid=uid, channel=channel)                    if ai_service.exists():  # 有正在使用的套餐,套餐结束时间保存为套餐有效期                        ai_service.update(updTime=nowTime, use_status=1, orders_id=orderID, endTime=endTime)                    else:                        AiService.objects.create(uid=uid, channel=channel, detect_status=1, addTime=nowTime,                                                 updTime=nowTime, endTime=endTime, use_status=1, orders_id=orderID)                # 如果存在序列号,消息提示用序列号                device_name = CommonService.query_serial_with_uid(uid=UID)                datetime = time.strftime("%Y-%m-%d", time.localtime())                sys_msg_text_list = [                    '温馨提示:尊敬的客户,您的' + device_name + '设备在' + datetime + '已成功续订云存套餐',                    'Dear customer,you already subscribed the cloud storage package successfully for device ' + device_name + ' on ' + time.strftime(                        "%b %dth,%Y", time.localtime())]                if pay_type == 1:                    lang = 'en'                else:                    lang = 'cn'                CloudStorage.CloudStorageView().do_vod_msg_notice(UID, channel, userid, lang,                                                                  sys_msg_text_list, 'SMS_219738485')                # 更新agreement                billing_agreement_update_attributes = [                    {                        "op": "replace",                        "path": "/",                        "value": {                            "description": orderID,                        }                    }                ]                update_status = billing_agreement.replace(billing_agreement_update_attributes)                # 记录新订单号和更新状态                PaypalWebHookEvent.objects.filter(id=paypal_webhook_event_qs.id).update(newOrderID=orderID,                                                                                        update_status=update_status)                PAY_LOGGER.info('{} PayPal周期扣款成功'.format(UID))                return HttpResponse('success')        except Exception as e:            PAY_LOGGER.info('PayPal周期扣款异常: errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))            return HttpResponse('fail', status=500)    @staticmethod    def config_match_region(uid, country_id):        """        判断用户地区是否跟服务器地区匹配        @param uid: uid        @param country_id: 国家表id        @return: bool        """        country_qs = CountryModel.objects.filter(id=country_id).values('region_id')        # 不确定用户地区信息,默认美洲        if not country_qs.exists():            if CONFIG_INFO == CONFIG_EUR:                PAY_LOGGER.info(                    'PayPal周期扣款失败---不确定地区的用户请求欧洲服,uid:{},country_id:{}'.format(uid, country_id))                return False            else:                return True        region_id = country_qs[0]['region_id']        PAY_LOGGER.info('uid{}的用户地区信息: country_id:{}, region_id:{}'.format(uid, country_id, region_id))        if (CONFIG_INFO == CONFIG_US and region_id == 4) or (CONFIG_INFO == CONFIG_EUR and region_id != 4):            PAY_LOGGER.info('PayPal周期扣款失败---服务器跟用户地区不匹配')            return False        return True    def do_subscription_break_notify(self, request_dict, request, response):        logger = logging.getLogger('pay')        logger.info('--------进入订阅失败,付款失败,暂停--------')        json_agreement_str = request.body.decode("utf-8")        json_obj = json.loads(json_agreement_str)        header = request.META        paypal_body = json_obj.get('resource')        logger.info('----主体信息----')        logger.info(json_agreement_str)        logger.info('----进入订阅失败头部信息----')        logger.info(header)        try:            transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID', None)            transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME', None)            cert_url = header.get('HTTP_PAYPAL_CERT_URL', None)            transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG', None)            auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO', None)            event_type = json_obj.get('event_type')            summary = json_obj.get('summary')            resource_type = json_obj.get('resource_type')            paypal_transaction_id = paypal_body.get('id')            amount = paypal_body.get('amount')            # self.get_plan_desc('P-4CG284532S612303METMEINY')            paypalrestsdk.configure(PAYPAL_CRD['Zosi'])            response = paypalrestsdk.WebhookEvent.verify(                transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID['Zosi']['subscriptionBreakNotify'],                json_agreement_str, cert_url, transmission_sig, auth_algo)            logger.info('----验证签名----')            logger.info(response)            if not response:                paypalrestsdk.configure(PAYPAL_CRD['Vsees'])                response = paypalrestsdk.WebhookEvent.verify(                    transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID['Vsees']['subscriptionBreakNotify'],                    json_agreement_str, cert_url, transmission_sig, auth_algo)                logger.info('----验证签名----')                logger.info(response)                if not response:                    return HttpResponse('Fail', status=500)            event_type_code = 0            billing_agreement_id = ''            if event_type == 'PAYMENT.SALE.COMPLETED':                event_type_code = 1                billing_agreement_id = paypal_body.get('billing_agreement_id')            elif event_type == 'PAYMENT.SALE.REVERSED':                billing_agreement_id = paypal_body.get('billing_agreement_id')                event_type_code = 2            elif event_type == 'BILLING.SUBSCRIPTION.CANCELLED':                billing_agreement_id = paypal_body.get('id')                event_type_code = 3            elif event_type == 'BILLING.SUBSCRIPTION.SUSPENDED':                billing_agreement_id = paypal_body.get('id')                event_type_code = 4            elif event_type == 'BILLING.SUBSCRIPTION.PAYMENT.FAILED':                billing_agreement_id = paypal_body.get('id')                event_type_code = 5            elif event_type == 'PAYMENT.SALE.REFUNDED':                billing_agreement_id = paypal_body.get('billing_agreement_id')                event_type_code = 6            PaypalWebHookEventInsert = {                'webhook_event_id': json_obj.get('id'),                'resource_type': resource_type,                'event_type': event_type_code,                'summary': summary,                'trade_no': paypal_transaction_id,                'resource': json_agreement_str,                'created_time': int(time.time()),            }            if not billing_agreement_id:                # 记录钩子日志                PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)                return HttpResponse('success')            billing_agreement = paypalrestsdk.BillingAgreement.find(billing_agreement_id)            # 记录钩子日志            PaypalWebHookEventInsert['agreement_desc'] = repr(billing_agreement)            PaypalWebHookEventInsert['agreement_id'] = billing_agreement_id            PaypalWebHookEventInsert['orderID'] = billing_agreement.description            PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)            return HttpResponse('success')        except Exception as e:            print(e)            logger.info('----进入订阅失败----')            logger.info('do_paypal_webhook_notify支付失败:----')            logger.info("错误行数:{errLine}".format(errLine=e.__traceback__.tb_lineno))            logger.info(repr(e))            return HttpResponse('fail', status=500)    def get_plan_desc(self, plan_id):        paypalrestsdk.configure(PAYPAL_CRD)        billing_plan = paypalrestsdk.BillingPlan.find(plan_id)        print("Got Billing Plan Details for Billing Plan[%s]" % (billing_plan.id))        exit()class payCycle(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()        token = request_dict.get('token', None)        # 设备主键uid        tko = TokenObject(token)        response.lang = tko.lang        if tko.code != 0:            return response.json(tko.code)        userID = tko.userID        if operation is None:            return response.json(444, 'error path')        elif operation == 'queryPayCycle':  # paypal成功订阅回调            return self.do_query_pay_cycle(request_dict, userID, response)        elif operation == 'cancelPayCycle':  # 取消自动续费            return self.do_cancel_pay_cycle(request_dict, userID, response)    def do_query_pay_cycle(self, request_dict, userID, response):        lang = request_dict.get('lang', 'en')        uid = request_dict.get('uid', None)        orderObject = Order_Model.objects.filter(userID=userID, status=1, rank__lang__lang=lang).annotate(            rank__title=F('rank__lang__title'), rank__content=F('rank__lang__content'))        if uid:            orderObject = orderObject.filter(UID=uid)        orderObject = orderObject.filter(~Q(agreement_id=''))        if not orderObject.exists():            return response.json(0, {'data': [], 'count': 0})        orderQuery = orderObject.values("orderID", "UID", "channel", "desc", "price", "currency",                                        "addTime",                                        "updTime", "paypal", "rank__day", "payType",                                        "rank__price", "status",                                        "rank__lang__content", "rank__lang__title", "rank__currency",                                        "rank_id", "rank__expire", "agreement_id").order_by('addTime')        new_data = []        values = []        for d in orderQuery:            if d['agreement_id'] not in values:                new_data.append(d)                values.append(d['agreement_id'])        count = len(new_data)        return response.json(0, {'data': new_data, 'count': count})    def do_cancel_pay_cycle(self, request_dict, userID, response):        orderID = request_dict.get('orderID', 'None')        orderObject = Order_Model.objects.filter(orderID=orderID)        orderObject = orderObject.filter(~Q(agreement_id='')).values("agreement_id", "app_type")        if not orderObject.exists():            return response.json(800)        app_type = orderObject[0]['app_type']        if app_type == 1:            paypalrestsdk.configure(PAYPAL_CRD['Zosi'])        elif app_type == 2:            paypalrestsdk.configure(PAYPAL_CRD['Vsees'])        BILLING_AGREEMENT_ID = orderObject[0]['agreement_id']        try:            billing_agreement = paypalrestsdk.BillingAgreement.find(BILLING_AGREEMENT_ID)            if billing_agreement.state != 'Active':                Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')                return response.json(0)            cancel_note = {"note": "Canceling the agreement"}            if billing_agreement.cancel(cancel_note):                Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')                return response.json(0)            else:                return response.json(10052)        except Exception as e:            return response.json(10052)
 |