Source code for appstoreserverlibrary.receipt_utility
# Copyright (c) 2023 Apple Inc. Licensed under MIT License.
from base64 import b64decode
from typing import Optional
import asn1
import base64
import re
PKCS7_OID = "1.2.840.113549.1.7.2"
IN_APP_ARRAY = 17
TRANSACTION_IDENTIFIER = 1703
ORIGINAL_TRANSACTION_IDENTIFIER = 1705
[docs]
class ReceiptUtility:
def _decode_octet_string(self, octet_string: bytes):
decoder = asn1.Decoder()
decoder.start(octet_string)
_, value = decoder.read()
return value
[docs]
def extract_transaction_id_from_app_receipt(self, app_receipt: str) -> Optional[str]:
"""
Extracts a transaction id from an encoded App Receipt. Throws if the receipt does not match the expected format.
*NO validation* is performed on the receipt, and any data returned should only be used to call the App Store Server API.
:param appReceipt: The unmodified app receipt
:return: A transaction id from the array of in-app purchases, null if the receipt contains no in-app purchases
"""
try:
val = self._decode_octet_string(b64decode(app_receipt, validate=True))
found_oid = val[0]
if found_oid != PKCS7_OID:
raise ValueError()
inner_value = val[1][0][2][1][0]
# Xcode uses nested OctetStrings, we extract the inner string in this case
value = self._decode_octet_string(inner_value)
# We are in the top-level sequence, work our way to the array of in-apps
for inner_value in value:
if inner_value[0] == IN_APP_ARRAY:
array_values = self._decode_octet_string(inner_value[2])
# In-app array
for array_value in array_values:
if array_value[0] == TRANSACTION_IDENTIFIER or array_value[0] == ORIGINAL_TRANSACTION_IDENTIFIER:
return self._decode_octet_string(array_value[2])
return None
except Exception as e:
raise ValueError(e)
[docs]
def extract_transaction_id_from_transaction_receipt(self, transaction_receipt: str) -> Optional[str]:
"""
Extracts a transaction id from an encoded transactional receipt. Throws if the receipt does not match the expected format.
*NO validation* is performed on the receipt, and any data returned should only be used to call the App Store Server API.
:param transactionReceipt: The unmodified transactionReceipt
:return: A transaction id, or null if no transactionId is found in the receipt
"""
decoded_top_level = base64.b64decode(transaction_receipt).decode('utf-8')
matching_result = re.search(r'"purchase-info"\s+=\s+"([a-zA-Z0-9+/=]+)";', decoded_top_level)
if matching_result:
decoded_inner_level = base64.b64decode(matching_result.group(1)).decode('utf-8')
inner_matching_result = re.search(r'"transaction-id"\s+=\s+"([a-zA-Z0-9+/=]+)";', decoded_inner_level)
if inner_matching_result:
return inner_matching_result.group(1)
return None