Skip to content

Commit e746f5d

Browse files
Merge pull request #95 from alexanderjordanbaker/AppStoreServerAPI112
Add support for App Store Server API v1.12 and App Store Server Notif…
2 parents 7d9c1c9 + 910a676 commit e746f5d

File tree

6 files changed

+91
-10
lines changed

6 files changed

+91
-10
lines changed

appstoreserverlibrary/api_client.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import calendar
44
import datetime
5-
from enum import IntEnum
5+
from enum import IntEnum, Enum
66
from typing import Any, Dict, List, Optional, Type, TypeVar, Union
77
from attr import define
88
import requests
@@ -450,6 +450,14 @@ def __init__(self, http_status_code: int, raw_api_error: Optional[int] = None, e
450450
except ValueError:
451451
pass
452452

453+
class GetTransactionHistoryVersion(str, Enum):
454+
V1 = "v1"
455+
"""
456+
.. deprecated:: 1.3.0
457+
"""
458+
459+
V2 = "v2"
460+
453461
class AppStoreServerAPIClient:
454462
def __init__(self, signing_key: bytes, key_id: str, issuer_id: str, bundle_id: str, environment: Environment):
455463
if environment == Environment.XCODE:
@@ -606,14 +614,15 @@ def get_notification_history(self, pagination_token: Optional[str], notification
606614

607615
return self._make_request("/inApps/v1/notifications/history", "POST", queryParameters, notification_history_request, NotificationHistoryResponse)
608616

609-
def get_transaction_history(self, transaction_id: str, revision: Optional[str], transaction_history_request: TransactionHistoryRequest) -> HistoryResponse:
617+
def get_transaction_history(self, transaction_id: str, revision: Optional[str], transaction_history_request: TransactionHistoryRequest, version: GetTransactionHistoryVersion = GetTransactionHistoryVersion.V1) -> HistoryResponse:
610618
"""
611619
Get a customer's in-app purchase transaction history for your app.
612620
https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history
613621
614622
:param transaction_id: The identifier of a transaction that belongs to the customer, and which may be an original transaction identifier.
615623
:param revision: A token you provide to get the next set of up to 20 transactions. All responses include a revision token. Note: For requests that use the revision token, include the same query parameters from the initial request. Use the revision token from the previous HistoryResponse.
616624
:param transaction_history_request: The request parameters that includes the startDate,endDate,productIds,productTypes and optional query constraints.
625+
:param version: The version of the Get Transaction History endpoint to use. V2 is recommended.
617626
:return: A response that contains the customer's transaction history for an app.
618627
:throws APIException: If a response was returned indicating the request could not be processed
619628
"""
@@ -645,7 +654,7 @@ def get_transaction_history(self, transaction_id: str, revision: Optional[str],
645654
if transaction_history_request.revoked is not None:
646655
queryParameters["revoked"] = [str(transaction_history_request.revoked)]
647656

648-
return self._make_request("/inApps/v1/history/" + transaction_id, "GET", queryParameters, None, HistoryResponse)
657+
return self._make_request("/inApps/" + version + "/history/" + transaction_id, "GET", queryParameters, None, HistoryResponse)
649658

650659
def get_transaction_info(self, transaction_id: str) -> TransactionInfoResponse:
651660
"""

appstoreserverlibrary/models/JWSRenewalInfoDecodedPayload.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .LibraryUtility import AttrsRawValueAware
1111
from .OfferType import OfferType
1212
from .PriceIncreaseStatus import PriceIncreaseStatus
13+
from .OfferDiscountType import OfferDiscountType
1314

1415
@define
1516
class JWSRenewalInfoDecodedPayload(AttrsRawValueAware):
@@ -140,4 +141,30 @@ class JWSRenewalInfoDecodedPayload(AttrsRawValueAware):
140141
The UNIX time, in milliseconds, that the most recent auto-renewable subscription purchase expires.
141142
142143
https://developer.apple.com/documentation/appstoreserverapi/renewaldate
144+
"""
145+
146+
currency: Optional[str] = attr.ib(default=None)
147+
"""
148+
The currency code for the renewalPrice of the subscription.
149+
150+
https://developer.apple.com/documentation/appstoreserverapi/currency
151+
"""
152+
153+
renewalPrice: Optional[int] = attr.ib(default=None)
154+
"""
155+
The renewal price, in milliunits, of the auto-renewable subscription that renews at the next billing period.
156+
157+
https://developer.apple.com/documentation/appstoreserverapi/renewalprice
158+
"""
159+
160+
offerDiscountType: Optional[OfferDiscountType] = OfferDiscountType.create_main_attr('rawOfferDiscountType')
161+
"""
162+
The payment mode of the discount offer.
163+
164+
https://developer.apple.com/documentation/appstoreserverapi/offerdiscounttype
165+
"""
166+
167+
rawOfferDiscountType: Optional[str] = OfferDiscountType.create_raw_attr('offerDiscountType')
168+
"""
169+
See offerDiscountType
143170
"""

appstoreserverlibrary/models/NotificationTypeV2.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ class NotificationTypeV2(str, Enum, metaclass=AppStoreServerLibraryEnumMeta):
2727
TEST = "TEST"
2828
RENEWAL_EXTENSION = "RENEWAL_EXTENSION"
2929
REFUND_REVERSED = "REFUND_REVERSED"
30-
EXTERNAL_PURCHASE_TOKEN = "EXTERNAL_PURCHASE_TOKEN"
30+
EXTERNAL_PURCHASE_TOKEN = "EXTERNAL_PURCHASE_TOKEN"
31+
ONE_TIME_CHARGE = "ONE_TIME_CHARGE"

tests/resources/models/signedRenewalInfo.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@
1212
"signedDate": 1698148800000,
1313
"environment": "LocalTesting",
1414
"recentSubscriptionStartDate": 1698148800000,
15-
"renewalDate": 1698148850000
15+
"renewalDate": 1698148850000,
16+
"renewalPrice": 9990,
17+
"currency": "USD",
18+
"offerDiscountType": "PAY_AS_YOU_GO"
1619
}

tests/test_api_client.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import unittest
55

66
from requests import Response
7-
from appstoreserverlibrary.api_client import APIError, APIException, AppStoreServerAPIClient
7+
from appstoreserverlibrary.api_client import APIError, APIException, AppStoreServerAPIClient, GetTransactionHistoryVersion
88
from appstoreserverlibrary.models.AccountTenure import AccountTenure
99
from appstoreserverlibrary.models.AutoRenewStatus import AutoRenewStatus
1010
from appstoreserverlibrary.models.ConsumptionRequest import ConsumptionRequest
@@ -220,7 +220,7 @@ def test_get_notification_history(self):
220220
]
221221
self.assertEqual(expected_notification_history, notification_history_response.notificationHistory)
222222

223-
def test_get_transaction_history(self):
223+
def test_get_transaction_history_v1(self):
224224
client = self.get_client_with_body_from_file('tests/resources/models/transactionHistoryResponse.json',
225225
'GET',
226226
'https://local-testing-base-url/inApps/v1/history/1234',
@@ -246,7 +246,44 @@ def test_get_transaction_history(self):
246246
subscriptionGroupIdentifiers=['sub_group_id', 'sub_group_id_2']
247247
)
248248

249-
history_response = client.get_transaction_history('1234', 'revision_input', request)
249+
history_response = client.get_transaction_history('1234', 'revision_input', request, GetTransactionHistoryVersion.V1)
250+
251+
self.assertIsNotNone(history_response)
252+
self.assertEqual('revision_output', history_response.revision)
253+
self.assertTrue(history_response.hasMore)
254+
self.assertEqual('com.example', history_response.bundleId)
255+
self.assertEqual(323232, history_response.appAppleId)
256+
self.assertEqual(Environment.LOCAL_TESTING, history_response.environment)
257+
self.assertEqual('LocalTesting', history_response.rawEnvironment)
258+
self.assertEqual(['signed_transaction_value', 'signed_transaction_value2'], history_response.signedTransactions)
259+
260+
def test_get_transaction_history_v2(self):
261+
client = self.get_client_with_body_from_file('tests/resources/models/transactionHistoryResponse.json',
262+
'GET',
263+
'https://local-testing-base-url/inApps/v2/history/1234',
264+
{'revision': ['revision_input'],
265+
'startDate': ['123455'],
266+
'endDate': ['123456'],
267+
'productId': ['com.example.1', 'com.example.2'],
268+
'productType': ['CONSUMABLE', 'AUTO_RENEWABLE'],
269+
'sort': ['ASCENDING'],
270+
'subscriptionGroupIdentifier': ['sub_group_id', 'sub_group_id_2'],
271+
'inAppOwnershipType': ['FAMILY_SHARED'],
272+
'revoked': ['False']},
273+
None)
274+
275+
request = TransactionHistoryRequest(
276+
sort=Order.ASCENDING,
277+
productTypes=[ProductType.CONSUMABLE, ProductType.AUTO_RENEWABLE],
278+
endDate=123456,
279+
startDate=123455,
280+
revoked=False,
281+
inAppOwnershipType=InAppOwnershipType.FAMILY_SHARED,
282+
productIds=['com.example.1', 'com.example.2'],
283+
subscriptionGroupIdentifiers=['sub_group_id', 'sub_group_id_2']
284+
)
285+
286+
history_response = client.get_transaction_history('1234', 'revision_input', request, GetTransactionHistoryVersion.V2)
250287

251288
self.assertIsNotNone(history_response)
252289
self.assertEqual('revision_output', history_response.revision)
@@ -397,7 +434,7 @@ def test_unknown_error(self):
397434
def test_get_transaction_history_with_unknown_environment(self):
398435
client = self.get_client_with_body_from_file('tests/resources/models/transactionHistoryResponseWithMalformedEnvironment.json',
399436
'GET',
400-
'https://local-testing-base-url/inApps/v1/history/1234',
437+
'https://local-testing-base-url/inApps/v2/history/1234',
401438
{'revision': ['revision_input'],
402439
'startDate': ['123455'],
403440
'endDate': ['123456'],
@@ -420,7 +457,7 @@ def test_get_transaction_history_with_unknown_environment(self):
420457
subscriptionGroupIdentifiers=['sub_group_id', 'sub_group_id_2']
421458
)
422459

423-
history_response = client.get_transaction_history('1234', 'revision_input', request)
460+
history_response = client.get_transaction_history('1234', 'revision_input', request, GetTransactionHistoryVersion.V2)
424461

425462
self.assertIsNone(history_response.environment)
426463
self.assertEqual("LocalTestingxxx", history_response.rawEnvironment)

tests/test_decoded_payloads.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def test_renewal_info_decoding(self):
107107
self.assertEqual("LocalTesting", renewal_info.rawEnvironment)
108108
self.assertEqual(1698148800000, renewal_info.recentSubscriptionStartDate)
109109
self.assertEqual(1698148850000, renewal_info.renewalDate)
110+
self.assertEqual(9990, renewal_info.renewalPrice)
111+
self.assertEqual("USD", renewal_info.currency)
112+
self.assertEqual(OfferDiscountType.PAY_AS_YOU_GO, renewal_info.offerDiscountType)
113+
self.assertEqual("PAY_AS_YOU_GO", renewal_info.rawOfferDiscountType)
110114

111115
def test_notification_decoding(self):
112116
signed_notification = create_signed_data_from_json('tests/resources/models/signedNotification.json')

0 commit comments

Comments
 (0)