소스 검색

苹果内购 创建订单、认证收据、自动续费订阅

linhaohong 1 년 전
부모
커밋
f8e7191fdd
3개의 변경된 파일105개의 추가작업 그리고 59개의 파일을 삭제
  1. 21 0
      Controller/CloudStorage.py
  2. 37 59
      Controller/InAppPurchaseController.py
  3. 47 0
      Object/AppleInAppPurchaseSubscriptionObject.py

+ 21 - 0
Controller/CloudStorage.py

@@ -46,6 +46,7 @@ from Service.PayService import PaymentService
 from Service.VodHlsService import SplitVodHlsObject
 from Object.ApplePayObject import ApplePayObject
 from Object.UnionPayObject import UnionPayObject
+from Object.AppleInAppPurchaseSubscriptionObject import InAppSubscription
 
 ssl._create_default_https_context = ssl._create_unverified_context
 LOGGER = logging.getLogger('info')
@@ -1667,6 +1668,26 @@ class CloudStorageView(View):
                                        ai_rank_id=1, store_meal_name=store_meal_name)
             return JsonResponse(status=200, data={'result_code': 0, 'reason': 'success', 'result': {"orderID": tn},
                                                   'error_code': 0})
+        elif pay_type == 5:
+            # 内购订阅型订单 之前订阅过任意套餐返回不可再次订阅
+            if store_qs[0]['cycle_config_id']:
+                # 检查Paypal订阅
+                check_subscribe_Paypal = Paypal.checkSubscriptions(user_id, uid, rank)
+                # 检查内购订阅
+                in_app_subscription = InAppSubscription()
+                check_subscribe_InApp = in_app_subscription.check_subscriptions(uid)
+                if not check_subscribe_Paypal or check_subscribe_InApp:
+                    logger.info(f'设备订阅过套餐Paypal:{not check_subscribe_Paypal}, AppleInApp:{check_subscribe_InApp}')
+                    return response.json(10050)
+            Order_Model.objects.create(
+                orderID=order_id, UID=uid, channel=channel, userID_id=user_id, desc=content, payType=pay_type,
+                payTime=now_time, price=price, currency=currency, addTime=now_time, updTime=now_time,
+                isSelectDiscounts=is_select_discount, order_type=order_type, commodity_code=commodity_code,
+                commodity_type=commodity_type, rank_id=rank, ai_rank_id=1, status=1, create_vod=1,
+                store_meal_name=store_meal_name)
+            return JsonResponse(status=200,
+                                data={'result_code': 0, 'reason': 'success', 'result': {"orderID": order_id},
+                                      'error_code': 0})
 
     def do_experience_order(self, request_dict, user_id, response):  # 生成体验订单
         """

+ 37 - 59
Controller/InAppPurchaseController.py

@@ -61,35 +61,26 @@ class InAppPurchaseView(View):
         @return: response
         """
         receipt = request_dict.get('receipt', None)
+        order_id = request_dict.get('orderId', None)
         uid = request_dict.get('uid', None)
         lang = request_dict.get('lang', 'en')
         channel = request_dict.get('channel', None)
+
         logger = logging.getLogger('apple_pay')
-        logger.info(f"认证交易receipt:{receipt}")
+        logger.info(f"认证交易receipt:{receipt}, 订单orderId:{order_id}")
 
-        if not all([receipt, uid, channel]):
+        if not all([receipt, uid, channel, order_id]):
             return response.json(444)
 
         # redis加锁,防止订单重复
         redis_obj = RedisObject()
-        redis_key = uid + 'in_app_purchase'
+        redis_key = order_id + 'in_app_purchase'
         is_lock = redis_obj.CONN.setnx(redis_key, 1)
         redis_obj.CONN.expire(redis_key, 60)
         # if not is_lock:
         #     return response.json(5)
 
         try:
-            device_info_qs = Device_Info.objects.filter(userID_id=user_id, UID=uid, isShare=False, isExist=1).values(
-                'vodPrimaryUserID',
-                'vodPrimaryMaster')
-            if not device_info_qs.exists():
-                return response.json(12)
-
-            device_info_qs = Device_Info.objects.filter(Q(UID=uid), ~Q(vodPrimaryUserID='')).values('vodPrimaryUserID')
-            if device_info_qs.exists():
-                if device_info_qs[0]['vodPrimaryUserID'] != user_id:
-                    return response.json(10033)
-
             # 从交易信息中获取product_id
             key_path = '{}/Ansjer/file/in_app_purchase/SubscriptionKey_N42WMFCV6A.p8'.format(BASE_DIR)
             with open(key_path, 'rb') as file:
@@ -111,6 +102,9 @@ class InAppPurchaseView(View):
 
             transaction_info = client.get_transaction_info(transaction_id)
             signed_transaction_info = transaction_info.signedTransactionInfo
+            if Order_Model.objects.filter(payType=5, transaction_id=transaction_id).exists():
+                logger.info(f"该transaction_id已订阅过:{transaction_id}")
+                return response.json(10048)
 
             root_certificates = []
             for cert_name in [
@@ -130,49 +124,32 @@ class InAppPurchaseView(View):
             payload = signed_data_verifier.verify_and_decode_signed_transaction(signed_transaction_info)
 
             product_id = None
-            originalTransaction_id = None
-            agreement_id = ""
-            if payload and payload.productId and payload.originalTransactionId:
+            original_transaction_id = ""
+
+            if payload and payload.productId:
                 product_id = payload.productId
-                originalTransaction_id = payload.originalTransactionId
 
-            if not product_id or not originalTransaction_id:
+            if not product_id:
                 pay_result_url = CommonService.get_payment_status_url(lang, 'fail')
                 return response.json(0, {'url': pay_result_url})
 
-            # 之前订阅过任意套餐返回不可再次订阅
-            if Store_Meal.objects.filter(product_id=product_id, cycle_config_id__isnull=False).exists():
-                agreement_id = originalTransaction_id
+            if payload.rawType == "Auto-Renewable Subscription":
+                original_transaction_id = payload.originalTransactionId
 
-            pay_type = PAY_TYPE_IN_APP_PURCHASE
             now_time = int(time.time())
-
-            store_qs = Store_Meal.objects.filter(
-                product_id=product_id, lang__lang=lang, is_show=0). \
-                values(
+            order_qs = Order_Model.objects.filter(orderID=order_id, UID=uid).values("rank_id")
+            if not order_qs.exists():
+                return response.json(173)
+            store_qs = Store_Meal.objects.filter(id=order_qs[0]['rank_id'], product_id=product_id).values(
                 'id', 'currency', 'price', 'lang__content', 'day', 'commodity_type', 'lang__title', 'expire',
                 'commodity_code', 'discount_price', 'bucket_id', 'bucket__mold', 'cycle_config_id', 'is_ai')
             if not store_qs.exists():
                 return response.json(173)
 
-            order_id = CommonService.createOrderID()
-            rank_id = store_qs[0]['id']
             bucket_id = store_qs[0]['bucket_id']
-            currency = store_qs[0]['currency']
-            price = store_qs[0]['price']
             is_ai = store_qs[0]['is_ai']
             expire = store_qs[0]['expire']
             end_time = CommonService.calcMonthLater(expire)
-            content = store_qs[0]['lang__content']
-            commodity_code = store_qs[0]['commodity_code']
-            commodity_type = store_qs[0]['commodity_type']
-            order_type = 1 if is_ai else 0
-            store_meal_qs = Store_Meal.objects.filter(id=rank_id, 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 = '未知套餐'
 
             # 查询设备是否已开过云存
             use_flag = True
@@ -210,13 +187,9 @@ class InAppPurchaseView(View):
                         uid=uid, channel=channel, detect_status=1, use_status=1, orders_id=order_id,
                         addTime=now_time, updTime=now_time, endTime=end_time)
 
-            Order_Model.objects.create(
-                orderID=order_id, UID=uid, channel=channel, userID_id=user_id, desc=content, payType=pay_type,
-                payTime=now_time, price=price, currency=currency, addTime=now_time, updTime=now_time,
-                order_type=order_type, commodity_code=commodity_code, commodity_type=commodity_type, rank_id=rank_id,
-                ai_rank_id=1, status=1, create_vod=1, store_meal_name=store_meal_name, uid_bucket_id=uid_bucket_id,
-                agreement_id=agreement_id
-            )
+            Order_Model.objects.filter(orderID=order_id).update(status=1, uid_bucket_id=uid_bucket_id,
+                                                                transaction_id=transaction_id,
+                                                                original_transaction_id=original_transaction_id)
 
             # 发送云存开通信息
             date_time = time.strftime("%Y-%m-%d", time.localtime())
@@ -349,19 +322,24 @@ class InAppPurchaseView(View):
                         decoded_payload.data.signedTransactionInfo)
 
                     # originalTransactionId 原始购买的交易标识符
-                    originalTransaction_id = decoded_transaction_information.originalTransactionId
+                    original_transaction_id = decoded_transaction_information.originalTransactionId
+                    transaction_id = decoded_transaction_information.transactionId
 
-                    logger.info(f"App Store服务器通知originalTransactionId原始购买的交易标识符{originalTransaction_id}")
-                    if not originalTransaction_id:
+                    logger.info(
+                        f"App Store服务器通知originalTransactionId原始购买的交易标识符{original_transaction_id}")
+                    if not original_transaction_id:
                         logger.info(f"App Store服务器通知originalTransactionId原始购买的交易标识符为空, 返回状态 400")
                         return HttpResponse(status=400)
                     else:
-                        ord_order = Order_Model.objects.filter(agreement_id=originalTransaction_id).order_by(
-                            'addTime').first()
-                        channel = ord_order.channel
-                        uid = ord_order.uid
-                        pay_type = ord_order.payType
-                        user_id = ord_order.userID_id
+                        ord_order = Order_Model.objects.filter(original_transaction_id=original_transaction_id).order_by(
+                            '-addTime')
+                        if not ord_order.exists():
+                            logger.info(f"App Store服务器通知未查询到旧订单信息, 返回状态 400")
+                            return HttpResponse(status=400)
+                        channel = ord_order[0]["channel"]
+                        uid = ord_order[0]["uid"]
+                        pay_type = ord_order[0]["payType"]
+                        user_id = ord_order[0]["userID_id"]
 
                         store_qs = Store_Meal.objects.filter(id=ord_order.rank). \
                             values(
@@ -441,7 +419,8 @@ class InAppPurchaseView(View):
                             order_type=order_type, commodity_code=commodity_code, commodity_type=commodity_type,
                             rank_id=rank_id,
                             ai_rank_id=1, status=1, create_vod=1, store_meal_name=store_meal_name,
-                            uid_bucket_id=uid_bucket_id, agreement_id=originalTransaction_id
+                            uid_bucket_id=uid_bucket_id, transaction_id=transaction_id,
+                            original_transaction_id=original_transaction_id
                         )
 
                         # 发送云存开通信息
@@ -493,7 +472,6 @@ class InAppPurchaseView(View):
                     # PRODUCT_NOT_FOR_SALE,则表示订阅已过期,因为在订阅尝试续订时,产品已不可购买。
                     # 没有子类型的通知表示订阅因其他原因过期。
                     pass
-
                 else:
                     logger.info(f"App Store服务器通知decoded_payload.rawNotificationType 未处理")
                     return HttpResponse(status=500)

+ 47 - 0
Object/AppleInAppPurchaseSubscriptionObject.py

@@ -0,0 +1,47 @@
+from django.db.models import Q
+
+from Ansjer.config import LOGGER, CONFIG_INFO, CONFIG_TEST, PAY_TYPE_IN_APP_PURCHASE, BASE_DIR
+from appstoreserverlibrary.api_client import AppStoreServerAPIClient, GetTransactionHistoryVersion
+from appstoreserverlibrary.models.Environment import Environment
+from appstoreserverlibrary.receipt_utility import ReceiptUtility
+from appstoreserverlibrary.models.HistoryResponse import HistoryResponse
+from appstoreserverlibrary.models.TransactionHistoryRequest import TransactionHistoryRequest, ProductType, Order
+from appstoreserverlibrary.signed_data_verifier import SignedDataVerifier
+
+from Model.models import Order_Model
+
+ENV = Environment.SANDBOX if CONFIG_INFO == CONFIG_TEST else Environment.PRODUCTION
+
+
+class InAppSubscription:
+    def __init__(self):
+        key_id = 'N42WMFCV6A'
+        issuer_id = '69a6de8c-789b-47e3-e053-5b8c7c11a4d1'
+        bundle_id = 'com.ansjer.zccloud'
+        environment = ENV
+        key_path = '{}/Ansjer/file/in_app_purchase/SubscriptionKey_N42WMFCV6A.p8'.format(BASE_DIR)
+        with open(key_path, 'rb') as file:
+            # 读取文件内容
+            private_key = file.read()
+        self.client = AppStoreServerAPIClient(private_key, key_id, issuer_id, bundle_id, environment)
+
+    def check_subscriptions(self, uid):
+        """
+        检查用户是否订阅
+        :param uid:
+        :return: True/False True表示已订阅
+        """
+        has_order_qs = Order_Model.objects.filter(UID=uid, payType=5).exclude(original_transaction_id="")
+        if not has_order_qs.exists():
+            return False
+        has_order = has_order_qs.values('transaction_id', 'orderID', 'addTime').order_by('-addTime')
+        subscription_statuses = self.client.get_all_subscription_statuses(has_order[0]['transaction_id'])
+        if subscription_statuses.data:
+            for subscription_status in subscription_statuses.data:
+                for subscription_status_item in subscription_status.lastTransactions:
+                    if str(subscription_status_item.status) == "Status.ACTIVE":
+                        return True
+            return False
+        else:
+            return False
+