Forráskód Böngészése

苹果内购退款

linhaohong 9 hónapja
szülő
commit
4671869203

+ 140 - 1
AdminController/ServeManagementController.py

@@ -12,6 +12,7 @@ import paypalrestsdk
 import requests
 import xlrd
 import xlwt
+from django.core.paginator import Paginator
 from django.db import transaction, connection
 from django.db.models import F, Sum, Count, Q
 from django.http import HttpResponse, StreamingHttpResponse
@@ -25,7 +26,8 @@ from Model.models import VodBucketModel, CDKcontextModel, Store_Meal, Order_Mode
     UID_Bucket, ExperienceContextModel, Lang, CloudLogModel, UidSetModel, Unused_Uid_Meal, \
     Device_Info, DeviceTypeModel, UnicomComboOrderInfo, AiService, CountryModel, \
     Device_User, AbnormalOrder, DailyReconciliation, StsCrdModel, LogModel, \
-    InAppPurchasePackage
+    InAppPurchasePackage, InAppRefund
+from Object.AppleInAppPurchaseSubscriptionObject import InAppPurchase
 from Object.ResponseObject import ResponseObject
 from Object.TokenObject import TokenObject
 from Object.UnicomObject import UnicomObjeect
@@ -130,6 +132,14 @@ class serveManagement(View):
                 return self.deactivationPackage(request_dict, response)
             elif operation == 'paypal-cycle-cancel':  # 取消循环扣款
                 return self.paypal_cycle_cancel(request_dict, response)
+
+            # 苹果内购退款
+            elif operation == 'purchaseRefundList':
+                return self.purchase_refund_list(request_dict, response)
+            elif operation == 'editRefundPreference':
+                return self.edit_refund_preference(request_dict, response)
+            elif operation == 'addRefundOrder':
+                return self.add_refund_order(request_dict, response)
             else:
                 return response.json(404)
 
@@ -2462,3 +2472,132 @@ class serveManagement(View):
         except Exception as e:
             meg = '异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e))
             return response.json(500, meg)
+
+    @staticmethod
+    def purchase_refund_list(request_dict, response):
+        """
+        苹果内购退款列表
+        :param request_dict:
+        :param response:
+        :return:
+        """
+        order_id = request_dict.get("orderID", None)
+        uid = request_dict.get("uid", None)
+        apple_order = request_dict.get("appleOrder", None)
+        app_type = request_dict.get("appType", None)
+        page = int(request_dict.get('page', 0))  # 默认值设为 0
+        page_size = int(request_dict.get('pageSize', 10))  # 默认值设为 10
+
+        in_app_refund_qs = InAppRefund.objects.all()
+        if order_id:
+            in_app_refund_qs = in_app_refund_qs.filter(orderID=order_id)
+        if uid:
+            in_app_refund_qs = in_app_refund_qs.filter(uid=uid)
+        if apple_order and app_type:
+            app_type = int(app_type)
+            if app_type == 1:
+                bundle_id = "com.ansjer.zccloud"
+            elif app_type == 2:
+                bundle_id = "com.cloudlife.commissionf"
+            else:
+                return response.json(0)
+            in_app_purchase_obj = InAppPurchase(bundle_id=bundle_id)
+            client = in_app_purchase_obj.client
+            signed_data_verifier = in_app_purchase_obj.verifier
+            order_lookup_response = client.look_up_order_id(apple_order)
+            signed_transactions = order_lookup_response.signedTransactions
+            if signed_transactions:
+                transaction_payload = signed_data_verifier.verify_and_decode_signed_transaction(signed_transactions[0])
+                transaction_id = transaction_payload.transactionId
+                in_app_refund_qs = in_app_refund_qs.filter(transaction_id=transaction_id)
+            else:
+                return response.json(800)
+        # 分页
+        paginator = Paginator(in_app_refund_qs.order_by("-created_time"), page_size)  # 每页显示 page_size 条
+        in_app_refund_page = paginator.get_page(page)
+
+        in_app_refund_list = []
+        for in_app_refund in in_app_refund_page:
+            in_app_refund_list.append(
+                {
+                    "refundId": in_app_refund.id,
+                    "orderID": in_app_refund.orderID,
+                    "uid": in_app_refund.uid,
+                    "refundPreference": in_app_refund.refund_preference,
+                    "refundProgress": in_app_refund.refund_progress,
+                    "appType": in_app_refund.app_type,
+                    "createTime": in_app_refund.created_time,
+                    "updateTime": in_app_refund.updated_time,
+                    "putTime": in_app_refund.put_time,
+                }
+            )
+        return response.json(0, {
+                "inAppRefundList": in_app_refund_list,
+                "total": paginator.count,
+            })
+
+    @staticmethod
+    def edit_refund_preference(request_dict, response):
+        refund_id = request_dict.get("refundId", None)
+        refund_preference = request_dict.get("refundPreference", None)
+        if not all([refund_id, refund_preference]):
+            return response.json(444)
+        InAppRefund.objects.filter(id=refund_id).update(refund_preference=refund_preference)
+        return response.json(0)
+
+    @staticmethod
+    def add_refund_order(request_dict, response):
+        """
+        添加预备退款订单
+        :param request_dict:
+        :param response:
+        :return:
+        """
+        order_id = request_dict.get("orderID", None)
+        apple_order = request_dict.get("appleOrder", None)
+        app_type = request_dict.get("appType", None)
+
+        if not order_id and (not apple_order or not app_type):
+            return response.json(444)
+        orders_qs = Order_Model.objects.all()
+        if order_id:
+            orders_qs = orders_qs.filter(orderID=order_id)
+            if InAppRefund.objects.filter(orderID=order_id).exists():
+                return response.json(174)
+        if apple_order and app_type:
+            app_type = int(app_type)
+            if app_type == 1:
+                bundle_id = "com.ansjer.zccloud"
+            elif app_type == 2:
+                bundle_id = "com.cloudlife.commissionf"
+            else:
+                return response.json(0)
+            in_app_purchase_obj = InAppPurchase(bundle_id=bundle_id)
+            client = in_app_purchase_obj.client
+            signed_data_verifier = in_app_purchase_obj.verifier
+            order_lookup_response = client.look_up_order_id(apple_order)
+            signed_transactions = order_lookup_response.signedTransactions
+            if signed_transactions:
+                transaction_payload = signed_data_verifier.verify_and_decode_signed_transaction(signed_transactions[0])
+                transaction_id = transaction_payload.transactionId
+                orders_qs = orders_qs.filter(transaction_id=transaction_id)
+                if InAppRefund.objects.filter(orderID=order_id).exists():
+                    return response.json(174)
+            else:
+                return response.json(800)
+        if not orders_qs.exists():
+            return response.json(173, "订单不存在")
+
+        uid = orders_qs[0].UID
+        order_id = orders_qs[0].orderID
+        transaction_id = orders_qs[0].transaction_id
+        now_time = int(time.time())
+        InAppRefund.objects.create(
+                uid=uid, transaction_id=transaction_id,
+                orderID=order_id, refund_progress=4, app_type=app_type,
+                created_time=now_time, updated_time=now_time)
+        return response.json(0)
+
+
+
+

+ 98 - 56
Controller/InAppPurchaseController.py

@@ -36,10 +36,12 @@ from Model.models import Order_Model, Store_Meal, Device_Info, UID_Bucket, Unuse
     SysMsgModel, OrderPayLog, InAppRefund
 from Object.AWS.S3Email import S3Email
 from Object.AliSmsObject import AliSmsObject
+from Object.AppleInAppPurchaseSubscriptionObject import InAppPurchase
 from Object.RedisObject import RedisObject
 from Service.CommonService import CommonService
 
 ENV = Environment.SANDBOX if CONFIG_INFO == CONFIG_TEST else Environment.PRODUCTION
+logger = logging.getLogger('apple_pay')
 
 
 class InAppPurchaseView(View):
@@ -77,7 +79,6 @@ class InAppPurchaseView(View):
         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}, 订单orderId: {order_id}, uid: {uid}")
 
         if not all([receipt, uid, channel, order_id]):
@@ -287,7 +288,6 @@ class InAppPurchaseView(View):
 
     @classmethod
     def app_store_server_notifications(cls, request):
-        logger = logging.getLogger('apple_pay')
         logger.info('App Store服务器通知请求类型:{}'.format(request.method))
         logger.info('App Store服务器通知参数:{}'.format(request.POST))
         logger.info('App Store服务器通知请求body:{}'.format(request.body))
@@ -334,23 +334,27 @@ class InAppPurchaseView(View):
                 orders_qs.update(status=5, updTime=int(time.time()))
                 orderID = orders_qs[0].orderID
                 uid = orders_qs[0].UID
-                user_id = orders_qs[0].userID
-                # 3. 未使用则删除未使用套餐表,已使用过则删除设备正在使用套餐,并关闭设备云存
-                uid_bucket_qs = UID_Bucket.objects.filter(uid=uid, orderId=orderID, use_status=1, endTime__gt=int(time.time()))
+                uid_bucket_qs = UID_Bucket.objects.filter(uid=uid, orderId=orderID, use_status=1,
+                                                          endTime__gt=int(time.time()))
                 unused_uid_meal_qs = Unused_Uid_Meal.objects.filter(order_id=orderID)
-                ai_service_qs = AiService.objects.filter(uid=uid, orders=orderID, use_status=1, endTime__gt=int(time.time()))
+                ai_service_qs = AiService.objects.filter(uid=uid, orders=orderID, use_status=1,
+                                                         endTime__gt=int(time.time()))
                 if unused_uid_meal_qs.exists():
                     unused_uid_meal_qs.delete()
                 if uid_bucket_qs.exists():
-                    uid_bucket_qs.update(status=0, use_status=2, endTime=int(time.time()), updateTime=int(time.time()))
+                    uid_bucket_qs.update(status=0, use_status=2, endTime=int(time.time()),
+                                         updateTime=int(time.time()))
                 if ai_service_qs.exists():
-                    ai_service_qs.update(detect_status=0, use_status=2, endTime=int(time.time()), updTime=int(time.time()))
+                    ai_service_qs.update(detect_status=0, use_status=2, endTime=int(time.time()),
+                                         updTime=int(time.time()))
                     # 关闭ai
                     msg = {'commandType': 'AIDisable'}
                     thing_name = CommonService.query_serial_with_uid(uid)  # 存在序列号则为使用序列号作为物品名
                     topic_name = 'ansjer/generic/{}'.format(thing_name)
                     req_success = CommonService.req_publish_mqtt_msg(thing_name, topic_name, msg)
-                    LOGGER.info(f'App Store服务器通知用户退款, 关闭AI:{req_success}')
+                    logger.info(f'App Store服务器通知用户退款, 关闭AI:{req_success}')
+                InAppRefund.objects.filter(transaction_id=transaction_id).update(updated_time=int(time.time()),
+                                                                                 refund_progress=2)
 
             elif CONFIG_INFO == CONFIG_US:
                 url = "https://api.zositeche.com/inAppPurchase/AppStoreServerNotifications"
@@ -358,60 +362,98 @@ class InAppPurchaseView(View):
                 status_code = eur_response.status_code
 
         elif str(decoded_payload.rawNotificationType) == "CONSUMPTION_REQUEST":
-            # 从交易信息中获取product_id
-            key_path = '{}/Ansjer/file/in_app_purchase/SubscriptionKey_N42WMFCV6A.p8'.format(BASE_DIR)
-            with open(key_path, 'rb') as file:
-                # 读取文件内容
-                private_key = file.read()
-
-            key_id = 'N42WMFCV6A'
-            issuer_id = '69a6de8c-789b-47e3-e053-5b8c7c11a4d1'
-            bundle_id = 'com.ansjer.zccloud'
-            environment = ENV
-            client = AppStoreServerAPIClient(private_key, key_id, issuer_id, bundle_id, environment)
             decoded_transaction_information = verifier.verify_and_decode_signed_transaction(
                 decoded_payload.data.signedTransactionInfo)
             transaction_id = decoded_transaction_information.transactionId
+            app_account_token = decoded_transaction_information.appAccountToken
             orders_qs = Order_Model.objects.filter(transaction_id=transaction_id)
             if orders_qs.exists():
                 orderID = orders_qs[0].orderID
-                unused_uid_meal_qs = Unused_Uid_Meal.objects.filter(order_id=orderID)
-                uid_bucket_qs = UID_Bucket.objects.filter(orderId=orderID, endTime__gt=int(time.time()))
-                if unused_uid_meal_qs.exists():
-                    consumptionStatus = ConsumptionStatus.NOT_CONSUMED
-                    deliveryStatus = DeliveryStatus.DELIVERED_AND_WORKING_PROPERLY
-                elif uid_bucket_qs.exists():
-                    consumptionStatus = ConsumptionStatus.PARTIALLY_CONSUMED
-                    deliveryStatus = DeliveryStatus.DELIVERED_AND_WORKING_PROPERLY
-                elif UID_Bucket.objects.filter(orderId=orderID, endTime__lt=int(time.time())):
-                    consumptionStatus = ConsumptionStatus.FULLY_CONSUMED
-                    deliveryStatus = DeliveryStatus.DELIVERED_AND_WORKING_PROPERLY
-                else:
-                    consumptionStatus = ConsumptionStatus.UNDECLARED
-                    deliveryStatus = DeliveryStatus.DID_NOT_DELIVER_FOR_OTHER_REASON
-                in_app_refund_qs = InAppRefund.objects.filter(transaction_id=transaction_id).exists()
-                refundPreference = RefundPreference.PREFER_DECLINE
+                uid = orders_qs[0].UID
+                now_time = int(time.time())
+                put_time = now_time + 11.5 * 60 * 60
+                in_app_refund_qs = InAppRefund.objects.filter(transaction_id=transaction_id)
                 if in_app_refund_qs.exists():
-                    if in_app_refund_qs[0].refund_preference == 1:
-                        refundPreference = RefundPreference.PREFER_GRANT
-                consumption_request = ConsumptionRequest(
-                    customerConsented=True,
-                    consumptionStatus=consumptionStatus,
-                    platform=Platform.UNDECLARED,
-                    sampleContentProvided=True,
-                    deliveryStatus=deliveryStatus,
-                    appAccountToken="",
-                    accountTenure=AccountTenure.UNDECLARED,
-                    playTime=PlayTime.UNDECLARED,
-                    lifetimeDollarsRefunded=LifetimeDollarsRefunded.UNDECLARED,
-                    lifetimeDollarsPurchased=LifetimeDollarsPurchased.UNDECLARED,
-                    userStatus=UserStatus.ACTIVE,
-                    refundPreference=refundPreference,
-                )
-                client.send_consumption_data(transaction_id, consumption_request)
+                    in_app_refund_qs.update(refund_progress=0, updated_time=now_time,
+                                            put_time=put_time, app_account_token=app_account_token)
+                InAppRefund.objects.create(transaction_id=transaction_id, orderID=orderID,
+                                           uid=uid, app_type=1, created_time=now_time,
+                                           updated_time=now_time, put_time=put_time,
+                                           app_account_token=app_account_token)
             elif CONFIG_INFO == CONFIG_US:
-                    url = "https://api.zositeche.com/inAppPurchase/AppStoreServerNotifications"
-                    eur_response = requests.post(url=url, json=json.loads(request.body))
-                    status_code = eur_response.status_code
+                url = "https://api.zositeche.com/inAppPurchase/AppStoreServerNotifications"
+                eur_response = requests.post(url=url, json=json.loads(request.body))
+                status_code = eur_response.status_code
+
+        elif str(decoded_payload.rawNotificationType) == "REFUND_DECLINED":
+            decoded_transaction_information = verifier.verify_and_decode_signed_transaction(
+                decoded_payload.data.signedTransactionInfo)
+            transaction_id = decoded_transaction_information.transactionId
+            orders_qs = Order_Model.objects.filter(transaction_id=transaction_id)
+            if orders_qs.exists():
+                InAppRefund.objects.filter(transaction_id=transaction_id).update(refund_progress=3,
+                                                                                 updated_time=int(time.time()))
+            elif CONFIG_INFO == CONFIG_US:
+                url = "https://api.zositeche.com/inAppPurchase/AppStoreServerNotifications"
+                eur_response = requests.post(url=url, json=json.loads(request.body))
+                status_code = eur_response.status_code
 
         return HttpResponse(status=status_code)
+
+    @staticmethod
+    def put_refund_order():
+        put_time = int(time.time())
+        in_app_refund_qs = InAppRefund.objects.filter(refund_progress=0, put_time__lt=put_time)
+        for in_app_refund in in_app_refund_qs:
+            transaction_id = in_app_refund.transaction_id
+            app_type = in_app_refund.app_type
+            if app_type == 1:
+                bundle_id = "com.ansjer.zccloud"
+            elif app_type == 2:
+                bundle_id = "com.cloudlife.commissionf"
+            else:
+                return HttpResponse(status=200)
+            in_app_purchase_obj = InAppPurchase(bundle_id=bundle_id)
+            # AppStoreServerAPIClient 用于查询交易信息
+            client = in_app_purchase_obj.client
+            orderID = in_app_refund.orderID
+            app_account_token = in_app_refund.app_account_token
+            unused_uid_meal_qs = Unused_Uid_Meal.objects.filter(order_id=orderID)
+            uid_bucket_qs = UID_Bucket.objects.filter(orderId=orderID, endTime__gt=int(time.time()))
+            if unused_uid_meal_qs.exists():
+                consumptionStatus = ConsumptionStatus.NOT_CONSUMED
+                deliveryStatus = DeliveryStatus.DELIVERED_AND_WORKING_PROPERLY
+            elif uid_bucket_qs.exists():
+                consumptionStatus = ConsumptionStatus.PARTIALLY_CONSUMED
+                deliveryStatus = DeliveryStatus.DELIVERED_AND_WORKING_PROPERLY
+            elif UID_Bucket.objects.filter(orderId=orderID, endTime__lt=int(time.time())):
+                consumptionStatus = ConsumptionStatus.FULLY_CONSUMED
+                deliveryStatus = DeliveryStatus.DELIVERED_AND_WORKING_PROPERLY
+            else:
+                consumptionStatus = ConsumptionStatus.UNDECLARED
+                deliveryStatus = DeliveryStatus.DID_NOT_DELIVER_FOR_OTHER_REASON
+
+            if in_app_refund.refund_preference == 1:
+                refundPreference = RefundPreference.PREFER_GRANT
+            else:
+                refundPreference = RefundPreference.PREFER_DECLINE
+
+            consumption_request = ConsumptionRequest(
+                customerConsented=True,
+                consumptionStatus=consumptionStatus,
+                platform=Platform.UNDECLARED,
+                sampleContentProvided=True,
+                deliveryStatus=deliveryStatus,
+                appAccountToken=app_account_token,
+                accountTenure=AccountTenure.UNDECLARED,
+                playTime=PlayTime.UNDECLARED,
+                lifetimeDollarsRefunded=LifetimeDollarsRefunded.UNDECLARED,
+                lifetimeDollarsPurchased=LifetimeDollarsPurchased.UNDECLARED,
+                userStatus=UserStatus.ACTIVE,
+                refundPreference=refundPreference,
+            )
+            client.send_consumption_data(transaction_id, consumption_request)
+            logger.info(f'内购退款消费数据提交, 订单orderID:{orderID}, transaction_id:{transaction_id}')
+            in_app_refund.refund_progress = 1
+            in_app_refund.save()
+        return HttpResponse(status=200)

+ 6 - 0
Model/models.py

@@ -5145,12 +5145,18 @@ class UserSetStatus(models.Model):
 
 class InAppRefund(models.Model):
     id = models.AutoField(primary_key=True, verbose_name='自增标记ID')
+    uid = models.CharField(default='', max_length=32, db_index=True, verbose_name='设备uid')
     transaction_id = models.CharField(default='', max_length=32, verbose_name='苹果事务id')
     orderID = models.CharField(blank=True, max_length=20, verbose_name=u'订单id')
+    app_type = models.SmallIntegerField(default=0, verbose_name='应用类型') # 1是zosiSmart 2是Vsees
     # 0不同意 1同意
     refund_preference = models.IntegerField(default=0, verbose_name='是否同意退款')
+    # 0申请退款 1已提交退款建议 2退款成功 3退款失败 4提前添加退款建议
+    refund_progress = models.IntegerField(default=0, verbose_name='退款进度')
+    app_account_token = models.TextField(blank=True, default='', verbose_name=u'参数内容')
     created_time = models.IntegerField(default=0, verbose_name='创建时间')
     updated_time = models.IntegerField(default=0, verbose_name='更新时间')
+    put_time = models.IntegerField(default=0, verbose_name='提交时间')
 
     class Meta:
         db_table = 'in_app_refund'

+ 105 - 0
Object/AppleInAppPurchaseSubscriptionObject.py

@@ -0,0 +1,105 @@
+# @Author    : linhaohong
+# @File      : AppleInAppPurchaseSubscriptionObject.py
+# @Time      : 2024/9/4 11:58
+from appstoreserverlibrary.receipt_utility import ReceiptUtility
+from Ansjer.config import CONFIG_INFO, CONFIG_TEST, BASE_DIR, IN_APP_CONFIG
+from appstoreserverlibrary.api_client import AppStoreServerAPIClient
+from appstoreserverlibrary.models.Environment import Environment
+from appstoreserverlibrary.signed_data_verifier import SignedDataVerifier
+ENV = Environment.SANDBOX if CONFIG_INFO == CONFIG_TEST else Environment.PRODUCTION
+
+
+class InAppConfig:
+    def __init__(self, bundle_id: str):
+        """
+       初始化 AppConfig,加载与指定 bundle_id 对应的配置。
+       :param bundle_id: 应用的 bundle ID,用于检索相关配置。
+       """
+        config = IN_APP_CONFIG.get(bundle_id)
+        if not config:
+            raise ValueError(f"没有找到与 bundle_id '{bundle_id}' 对应的配置")
+        self.key_id = config['key_id']
+        self.issuer_id = config['issuer_id']
+        self.key_filename = config['key_filename']
+        self.cert_names = config['cert_names']
+        self.app_apple_id = config.get("app_apple_id")
+        self.environment = ENV
+
+
+class InAppPurchase:
+    def __init__(self, bundle_id="com.ansjer.zccloud"):
+        """
+        初始化 InAppSubscription,加载私钥并初始化客户端和解码器。
+        :param app_config: 包含内购相关配置的 AppConfig 实例。
+        """
+        app_config = InAppConfig(bundle_id)
+        self.bundle_id = bundle_id
+        self.config = app_config
+        self.private_key = self._load_private_key(self.config.key_filename)
+        self.client = self._initialize_client()
+        self.verifier = self._initialize_verifier()
+        self.receipt_util = self._initialize_receipt_util()
+
+    def _load_private_key(self, key_filename: str) -> bytes:
+        """
+        加载私钥文件并返回其内容。
+        :param key_filename: 私钥文件的名称。
+        :return: 返回私钥文件的内容,类型为 bytes。
+        """
+
+        key_path = f'{BASE_DIR}/Ansjer/file/in_app_purchase/{key_filename}'
+        try:
+            with open(key_path, 'rb') as file:
+                return file.read()
+        except FileNotFoundError:
+            raise ValueError(f"Private key file not found: {key_path}")
+
+    def _initialize_client(self) -> AppStoreServerAPIClient:
+        """
+        初始化 AppStoreServerAPIClient 实例,用于与 App Store 进行通信。
+        :return: 返回初始化后的 AppStoreServerAPIClient 实例。
+        """
+        return AppStoreServerAPIClient(
+            self.private_key,
+            self.config.key_id,
+            self.config.issuer_id,
+            self.bundle_id,
+            self.config.environment
+        )
+
+    def _initialize_verifier(self) -> SignedDataVerifier:
+        """
+       初始化 SignedDataVerifier 实例,用于内购验证签名数据。
+       :return: 返回初始化后的 SignedDataVerifier 实例。
+       """
+        root_certificates = [
+            self._load_certificate(cert_name)
+            for cert_name in self.config.cert_names
+        ]
+        enable_online_checks = True  # 根据需要设定在线检查是否启用
+        app_apple_id = None if CONFIG_INFO == CONFIG_TEST else self.config.app_apple_id
+
+        return SignedDataVerifier(
+            root_certificates,
+            enable_online_checks,
+            self.config.environment,
+            self.bundle_id,
+            app_apple_id
+        )
+
+    def _load_certificate(self, cert_name: str) -> bytes:
+        """
+        加载指定的根证书文件并返回其内容。
+        :param cert_name: 根证书文件的名称。
+        :return: 返回根证书文件的内容,类型为 bytes。
+        """
+        cert_path = f'{BASE_DIR}/Ansjer/file/in_app_purchase/{cert_name}'
+        try:
+            with open(cert_path, 'rb') as file:
+                return file.read()
+        except FileNotFoundError:
+            raise ValueError(f"Certificate file not found: {cert_path}")
+
+    def _initialize_receipt_util(self):
+        receipt_util = ReceiptUtility()
+        return receipt_util