Skip to content

Commit

Permalink
Merge pull request #55 from youknowone/recent-spec
Browse files Browse the repository at this point in the history
Adapt recent spec
  • Loading branch information
youknowone authored Jun 17, 2018
2 parents 7ec49bc + c4d4ccc commit 896303c
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 66 deletions.
3 changes: 1 addition & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
include README.rst
include LICENSE
include itunesiap/version.txt
include LICENSE
11 changes: 8 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@


def get_version():
with open('../itunesiap/version.txt') as f:
return f.read().strip()
with open('../itunesiap/__version__.py') as f:
s = f.readline().strip()
_, v = s.split('__version__ = ')
version = v.strip("'")
assert version.startswith('2.')
assert version[-1] in '0123456789'
return version

#
# itunes-iap documentation build configuration file, created by
Expand Down Expand Up @@ -79,7 +84,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
19 changes: 4 additions & 15 deletions itunesiap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
Itunes In-app Purchase verification api.
:copyright: (c) 2013 Jeong YunWon - 2014 Andy Briggs
:copyright: (c) 2013 Jeong YunWon
:license: 2-clause BSD.
"""

from six import PY3

from .__version__ import __version__
from .request import Request
from .receipt import Response, Receipt, InApp
from .shortcut import verify, aioverify
Expand All @@ -20,18 +19,8 @@
exc = exceptions
env = environment # env.default, env.sandbox, env.review

try:
import pkg_resources
except ImportError: # pragma: no cover
__version__ = VERSION = None
else:
__version__ = pkg_resources.resource_string(
'itunesiap', 'version.txt').strip()
if PY3:
__version__ = __version__.decode('ascii') # pragma: no cover
VERSION = tuple(int(v) for v in __version__.split('.'))


__all__ = (
'Request', 'Response', 'Receipt', 'InApp', 'verify', 'aioverify',
'__version__', 'Request', 'Response', 'Receipt', 'InApp',
'verify', 'aioverify',
'exceptions', 'exc', 'environment', 'env')
1 change: 1 addition & 0 deletions itunesiap/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '2.6.0'
121 changes: 95 additions & 26 deletions itunesiap/receipt.py
Original file line number Diff line number Diff line change
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 @@ -135,13 +151,19 @@ def _get(_self):
return value
setattr(self.__class__, name, property(_get))
elif name in self.__FIELD_ADAPTERS__:
adapter = self.__FIELD_ADAPTERS__[name]
if isinstance(adapter, tuple):
data_key, transform = adapter
else:
data_key = name
transform = adapter

def _get(_self):
field_filter = self.__FIELD_ADAPTERS__[name]
try:
value = _self._[name]
value = _self._[data_key]
except KeyError:
raise MissingFieldError(name)
return field_filter(value)
return transform(value)
setattr(self.__class__, name, lazy_property(_get))
else:
pass # unhandled. raise AttributeError
Expand Down Expand Up @@ -184,43 +206,86 @@ 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,
'expires_date_ms': ('expires_date', int),
'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 Down Expand Up @@ -266,18 +331,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 +352,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 +375,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
1 change: 0 additions & 1 deletion itunesiap/version.txt

This file was deleted.

14 changes: 10 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@


def get_version():
with open('itunesiap/version.txt') as f:
return f.read().strip()
with open('itunesiap/__version__.py') as f:
s = f.readline().strip()
_, v = s.split('__version__ = ')
version = v.strip("'")
assert version.startswith('2.')
assert version[-1] in '0123456789'
return version


def get_readme():
Expand All @@ -23,7 +28,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[:3] >= (3, 5, 3):
Expand All @@ -46,7 +52,7 @@ def get_readme():
'itunesiap',
),
package_data={
'itunesiap': ['version.txt']
'itunesiap': []
},
install_requires=install_requires,
tests_require=tests_require,
Expand Down
Loading

0 comments on commit 896303c

Please sign in to comment.