From 22f1a734ff8e8073168eb16cb0a7e647e9889fa2 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 27 Aug 2026 14:14:01 -0700 Subject: [PATCH] Adding support for App Store Server Notifications v2.17 --- .../models/ExternalPurchaseToken.py | 20 +++++++++++++++++++ appstoreserverlibrary/models/Subtype.py | 2 ++ appstoreserverlibrary/models/TokenType.py | 14 +++++++++++++ ...gnedExternalPurchaseTokenNotification.json | 4 +++- ...ernalPurchaseTokenSandboxNotification.json | 4 +++- tests/test_decoded_payloads.py | 7 +++++++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 appstoreserverlibrary/models/TokenType.py diff --git a/appstoreserverlibrary/models/ExternalPurchaseToken.py b/appstoreserverlibrary/models/ExternalPurchaseToken.py index 1a8989d6..23a67fac 100644 --- a/appstoreserverlibrary/models/ExternalPurchaseToken.py +++ b/appstoreserverlibrary/models/ExternalPurchaseToken.py @@ -5,6 +5,7 @@ import attr from .LibraryUtility import AttrsRawValueAware +from .TokenType import TokenType @define class ExternalPurchaseToken(AttrsRawValueAware): @@ -40,4 +41,23 @@ class ExternalPurchaseToken(AttrsRawValueAware): The bundle identifier of an app. https://developer.apple.com/documentation/appstoreservernotifications/bundleid + """ + + tokenType: Optional[TokenType] = TokenType.create_main_attr('rawTokenType') + """ + The type of an external purchase custom link token. + + https://developer.apple.com/documentation/appstoreservernotifications/tokentype + """ + + rawTokenType: Optional[str] = TokenType.create_raw_attr('tokenType') + """ + See tokenType + """ + + tokenExpirationDate: Optional[int] = attr.ib(default=None) + """ + The field of a custom link token that contains the UNIX date, in milliseconds, when the token expires. + + https://developer.apple.com/documentation/appstoreservernotifications/tokenexpirationdate """ \ No newline at end of file diff --git a/appstoreserverlibrary/models/Subtype.py b/appstoreserverlibrary/models/Subtype.py index d1736833..04879edf 100644 --- a/appstoreserverlibrary/models/Subtype.py +++ b/appstoreserverlibrary/models/Subtype.py @@ -27,3 +27,5 @@ class Subtype(str, Enum, metaclass=AppStoreServerLibraryEnumMeta): SUMMARY = "SUMMARY" FAILURE = "FAILURE" UNREPORTED = "UNREPORTED" + ACTIVE_TOKEN_REMINDER = "ACTIVE_TOKEN_REMINDER" + CREATED = "CREATED" diff --git a/appstoreserverlibrary/models/TokenType.py b/appstoreserverlibrary/models/TokenType.py new file mode 100644 index 00000000..2b028b50 --- /dev/null +++ b/appstoreserverlibrary/models/TokenType.py @@ -0,0 +1,14 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from enum import Enum + +from .LibraryUtility import AppStoreServerLibraryEnumMeta + +class TokenType(str, Enum, metaclass=AppStoreServerLibraryEnumMeta): + """ + The type of an external purchase custom link token. + + https://developer.apple.com/documentation/appstoreservernotifications/tokentype + """ + SERVICES = "SERVICES" + ACQUISITION = "ACQUISITION" diff --git a/tests/resources/models/signedExternalPurchaseTokenNotification.json b/tests/resources/models/signedExternalPurchaseTokenNotification.json index 479785b1..c51ed9fa 100644 --- a/tests/resources/models/signedExternalPurchaseTokenNotification.json +++ b/tests/resources/models/signedExternalPurchaseTokenNotification.json @@ -8,6 +8,8 @@ "externalPurchaseId": "b2158121-7af9-49d4-9561-1f588205523e", "tokenCreationDate": 1698148950000, "appAppleId": 55555, - "bundleId": "com.example" + "bundleId": "com.example", + "tokenType": "ACQUISITION", + "tokenExpirationDate": 1698149000000 } } \ No newline at end of file diff --git a/tests/resources/models/signedExternalPurchaseTokenSandboxNotification.json b/tests/resources/models/signedExternalPurchaseTokenSandboxNotification.json index 509cdd64..71a163dc 100644 --- a/tests/resources/models/signedExternalPurchaseTokenSandboxNotification.json +++ b/tests/resources/models/signedExternalPurchaseTokenSandboxNotification.json @@ -8,6 +8,8 @@ "externalPurchaseId": "SANDBOX_b2158121-7af9-49d4-9561-1f588205523e", "tokenCreationDate": 1698148950000, "appAppleId": 55555, - "bundleId": "com.example" + "bundleId": "com.example", + "tokenType": "ACQUISITION", + "tokenExpirationDate": 1698149000000 } } \ No newline at end of file diff --git a/tests/test_decoded_payloads.py b/tests/test_decoded_payloads.py index 20232480..ab738329 100644 --- a/tests/test_decoded_payloads.py +++ b/tests/test_decoded_payloads.py @@ -19,6 +19,7 @@ from appstoreserverlibrary.models.RevocationType import RevocationType from appstoreserverlibrary.models.Status import Status from appstoreserverlibrary.models.Subtype import Subtype +from appstoreserverlibrary.models.TokenType import TokenType from appstoreserverlibrary.models.TransactionReason import TransactionReason from appstoreserverlibrary.models.Type import Type from appstoreserverlibrary.models.AdvancedCommercePeriod import AdvancedCommercePeriod @@ -360,6 +361,9 @@ def check_environment_and_bundle_id(bundle_id: Optional[str], app_apple_id: Opti self.assertEqual(1698148950000, notification.externalPurchaseToken.tokenCreationDate) self.assertEqual(55555, notification.externalPurchaseToken.appAppleId) self.assertEqual("com.example", notification.externalPurchaseToken.bundleId) + self.assertEqual(TokenType.ACQUISITION, notification.externalPurchaseToken.tokenType) + self.assertEqual("ACQUISITION", notification.externalPurchaseToken.rawTokenType) + self.assertEqual(1698149000000, notification.externalPurchaseToken.tokenExpirationDate) def test_external_purchase_token_sandbox_notification_decoding(self): signed_external_purchase_token_notification = create_signed_data_from_json('tests/resources/models/signedExternalPurchaseTokenSandboxNotification.json') @@ -389,6 +393,9 @@ def check_environment_and_bundle_id(bundle_id: Optional[str], app_apple_id: Opti self.assertEqual(1698148950000, notification.externalPurchaseToken.tokenCreationDate) self.assertEqual(55555, notification.externalPurchaseToken.appAppleId) self.assertEqual("com.example", notification.externalPurchaseToken.bundleId) + self.assertEqual(TokenType.ACQUISITION, notification.externalPurchaseToken.tokenType) + self.assertEqual("ACQUISITION", notification.externalPurchaseToken.rawTokenType) + self.assertEqual(1698149000000, notification.externalPurchaseToken.tokenExpirationDate) def test_realtime_request_decoding(self): signed_realtime_request = create_signed_data_from_json('tests/resources/models/decodedRealtimeRequest.json')