Skip to content

Commit

Permalink
Adapt recent spec
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowone committed Jun 17, 2018
1 parent 28a74b2 commit 07ea797
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 40 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Expand Up @@ -79,7 +79,7 @@ def get_version():
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = []
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
Expand Down
112 changes: 89 additions & 23 deletions itunesiap/receipt.py
Expand Up @@ -3,9 +3,10 @@
A successful response returns a JSON object including receipts. To manipulate
them in convinient way, `itunes-iap` wrapped it with :class:`ObjectMapper`.
"""
import datetime
import warnings
import pytz
import dateutil.parser
import warnings
import json
from collections import defaultdict
from prettyexc import PrettyException
Expand All @@ -21,12 +22,21 @@
_warned_undocumented_fields = defaultdict(bool)
_warned_unlisted_field = defaultdict(bool)

'''
class ExpirationIntent(Enum):
CustomerCanceledTheirSubscription = 1
BillingError = 2
CustumerDidNotAgreeToARecentPriceIncrease = 3
ProductWasNotAvailableForPurchaseAtTheTimeOfRenewal = 4
UnknownError = 5
'''


class MissingFieldError(PrettyException, AttributeError, KeyError):
"""A Backward compatibility error."""


def _to_datetime(value):
def _rfc3339_to_datetime(value):
"""Try to parse Apple iTunes receipt date format.
By reference, they insists it is rfc3339:
Expand All @@ -50,6 +60,12 @@ def _to_datetime(value):
return d


def _ms_to_datetime(value):
nd = datetime.datetime.utcfromtimestamp(int(value) / 1000)
ad = nd.replace(tzinfo=pytz.UTC)
return ad


def _to_bool(data):
assert data in ('true', 'false'), \
("Cannot convert {0}, "
Expand Down Expand Up @@ -184,43 +200,85 @@ class Receipt(ObjectMapper):
This object encapsulate it to list of :class:`InApp` object in `in_app`
property.
See also: `<https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html>`_
:see: `<https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html>`_
"""
__OPAQUE_FIELDS__ = frozenset([
# app receipt fields
'bundle_id',
'application_version',
'original_application_version',
# in-app purchase receipt fields
'product_id',
'transaction_id',
'original_transaction_id',
'expires_date_formatted',
'app_item_id',
'version_external_identifier',
'web_order_line_item_id',
'auto_renew_product_id',
])
__FIELD_ADAPTERS__ = {
'receipt_creation_date': _to_datetime,
# app receipt fields
'receipt_creation_date': _rfc3339_to_datetime,
'receipt_creation_date_ms': int,
'receipt_expiration_date': _to_datetime,
'receipt_expiration_date_ms': int,
'original_purchase_date': _to_datetime,
'expiration_date': _rfc3339_to_datetime,
'expiration_date_ms': int,
# in-app purchase receipt fields
'quantity': int,
'purchase_date': _rfc3339_to_datetime,
'purchase_date_ms': int,
'original_purchase_date': _rfc3339_to_datetime,
'original_purchase_date_ms': int,
'request_date': _to_datetime,
'expires_date': _ms_to_datetime,
'expiration_intent': int,
'is_in_billing_retry_period': _to_bool,
'is_in_intro_offer_period': _to_bool,
'cancellation_date': _rfc3339_to_datetime,
'cancellation_reason': int,
'auto_renew_status': int,
'price_consent_status': int,
'request_date': _rfc3339_to_datetime,
'request_date_ms': int,
}
__DOCUMENTED_FIELDS__ = frozenset([
# app receipt fields
'bundle_id',
'in_app',
'application_version',
'original_application_version',
'receipt_creation_date',
'receipt_creation_date_ms',
'receipt_expiration_date',
'receipt_expiration_date_ms',
'expiration_date',
# in-app purchase receipt fields
'quantity',
'product_id',
'transaction_id',
'original_transaction_id',
'purchase_date', # _formatted value
'original_purchase_date',
'expires_date', # _ms value
'is_in_billing_retry_period',
'is_in_intro_offer_period',
'cancellation_date',
'cancellation_reason',
'app_item_id',
'version_external_identifier',
'web_order_line_item_id',
'auto_renew_status',
'auto_renew_product_id',
'price_consent_status',
])
__UNDOCUMENTED_FIELDS__ = frozenset([
# app receipt fields
'request_date',
'request_date_ms',
'version_external_identifier',
'original_purchase_date',
'receipt_creation_date_ms',
'expiration_date_ms',
# in-app purchase receipt fields
'purchase_date_ms',
'original_purchase_date_ms',
'expires_date_formatted',
'unique_identifier',
])

@lazy_property
Expand All @@ -244,6 +302,10 @@ def last_in_app(self):
return sorted(
self.in_app, key=lambda x: x['original_purchase_date_ms'])[-1]

@property
def expires_date_ms(self):
return self._['expires_date']


class Purchase(ObjectMapper):
"""The individual purchases.
Expand All @@ -266,18 +328,18 @@ class Purchase(ObjectMapper):
'original_transaction_id',
'web_order_line_item_id',
'unique_identifier',
'expires_date_formatted',
])
__FIELD_ADAPTERS__ = {
'quantity': int,
'purchase_date': _to_datetime,
'purchase_date': _rfc3339_to_datetime,
'purchase_date_ms': int,
'original_purchase_date': _to_datetime,
'original_purchase_date': _rfc3339_to_datetime,
'original_purchase_date_ms': int,
'expires_date': _to_datetime,
'expires_date_formatted': _to_datetime,
'expires_date': _rfc3339_to_datetime,
'expires_date_ms': int,
'is_trial_period': _to_bool,
'cancellation_date': _to_datetime,
'cancellation_date': _rfc3339_to_datetime,
'cancellation_date_ms': int,
'cancellation_reason': int,
}
Expand All @@ -287,19 +349,18 @@ class Purchase(ObjectMapper):
'transaction_id',
'original_transaction_id',
'purchase_date',
'purchase_date_ms', # de facto documented
'original_purchase_date',
'original_purchase_date_ms', # de facto documented
'expires_date',
'expires_date_ms', # de facto documented
'is_trial_period',
'cancellation_date',
'cancellation_date_ms', # de facto documented
'cancellation_reason',
'web_order_line_item_id',
])
__UNDOCUMENTED_FIELDS__ = frozenset([
'unique_identifier',
'purchase_date_ms',
'original_purchase_date_ms',
'cancellation_date_ms',
'expires_date_formatted', # legacy receipts has this field as actual "expires_date"
])

Expand All @@ -311,12 +372,17 @@ def __eq__(self, other):
@lazy_property
def expires_date(self):
if 'expires_date_formatted' in self:
return _to_datetime(self['expires_date_formatted'])
return _rfc3339_to_datetime(self['expires_date_formatted'])
try:
value = self['expires_date']
except KeyError:
raise MissingFieldError('expires_date')
return _to_datetime(value)
try:
int(value)
except ValueError:
return _rfc3339_to_datetime(value)
else:
return _ms_to_datetime(value)


class InApp(Purchase):
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -23,7 +23,8 @@ def get_readme():
'pytz',
]
tests_require = [
'pytest>=3.0.0', 'pytest-cov', 'tox', 'mock', 'patch',
'pytest>=3.0.0', 'pytest-cov', 'pytest-lazy-fixture', 'tox',
'mock', 'patch',
]

if sys.version_info[:2] >= (3, 5):
Expand Down
68 changes: 65 additions & 3 deletions tests/conftest.py
@@ -1,4 +1,4 @@

# coding: utf-8
import json
import itunesiap
import pytest
Expand Down Expand Up @@ -128,9 +128,9 @@ def itunes_autorenew_response1():


@pytest.fixture(scope='session')
def itunes_autorenew_response():
def itunes_autorenew_response2():
"""Contributed by Jonas Petersen @jox"""
return json.loads('''{
return json.loads(r'''{
"status": 0,
"environment": "Sandbox",
"receipt": {
Expand Down Expand Up @@ -694,3 +694,65 @@ def itunes_autorenew_response():
}
]
}''')


@pytest.fixture(scope='session')
def itunes_autorenew_response3():
"""Contributed by François Dupayrat @FrancoisDupayrat"""
return json.loads(r'''{
"auto_renew_status": 1,
"status": 0,
"auto_renew_product_id": "******************************",
"receipt":{
"original_purchase_date_pst":"2017-06-28 07:31:51 America/Los_Angeles",
"unique_identifier":"******************************",
"original_transaction_id":"******************************",
"expires_date":"1506524970000",
"transaction_id":"******************************",
"quantity":"1",
"product_id":"******************************",
"item_id":"******************************",
"bid":"******************************",
"unique_vendor_identifier":"******************************",
"web_order_line_item_id":"******************************",
"bvrs":"1.1.6",
"expires_date_formatted":"2017-09-27 15:09:30 Etc/GMT",
"purchase_date":"2017-09-27 15:04:30 Etc/GMT",
"purchase_date_ms":"1506524670000",
"expires_date_formatted_pst":"2017-09-27 08:09:30 America/Los_Angeles",
"purchase_date_pst":"2017-09-27 08:04:30 America/Los_Angeles",
"original_purchase_date":"2017-06-28 14:31:51 Etc/GMT",
"original_purchase_date_ms":"1498660311000"
},
"latest_receipt_info":{
"original_purchase_date_pst":"2017-06-28 07:31:51 America/Los_Angeles",
"unique_identifier":"******************************",
"original_transaction_id":"******************************",
"expires_date":"******************************",
"transaction_id":"******************************",
"quantity":"1",
"product_id":"******************************",
"item_id":"******************************",
"bid":"******************************",
"unique_vendor_identifier":"******************************",
"web_order_line_item_id":"******************************",
"bvrs":"1.1.6",
"expires_date_formatted":"2017-09-27 15:09:30 Etc/GMT",
"purchase_date":"2017-09-27 15:04:30 Etc/GMT",
"purchase_date_ms":"1506524670000",
"expires_date_formatted_pst":"2017-09-27 08:09:30 America/Los_Angeles",
"purchase_date_pst":"2017-09-27 08:04:30 America/Los_Angeles",
"original_purchase_date":"2017-06-28 14:31:51 Etc/GMT",
"original_purchase_date_ms":"1498660311000"
},
"latest_receipt":"******************************"
}''')


@pytest.fixture(params=[
pytest.lazy_fixture('itunes_autorenew_response1'),
pytest.lazy_fixture('itunes_autorenew_response2'),
pytest.lazy_fixture('itunes_autorenew_response3'),
])
def itunes_autorenew_response(request):
return request.param

0 comments on commit 07ea797

Please sign in to comment.