Skip to content

Commit

Permalink
added ability to auto retry on wrong environment request.
Browse files Browse the repository at this point in the history
  • Loading branch information
dotpot committed Feb 7, 2017
1 parent 4c6d971 commit 5cbdd00
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 9 deletions.
42 changes: 34 additions & 8 deletions inapppy/appstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from requests.exceptions import RequestException
import requests


# https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
# `Table 2-1 Status codes`
api_result_ok = 0
api_result_errors = {
21000: InAppPyValidationError('Bad json'),
Expand All @@ -12,6 +13,8 @@
21004: InAppPyValidationError('Shared secret mismatch'),
21005: InAppPyValidationError('Server is unavailable'),
21006: InAppPyValidationError('Subscription has expired'),

# two following errors can use auto_retry_wrong_env_request.
21007: InAppPyValidationError('Sandbox receipt was sent to the production env'),
21008: InAppPyValidationError('Production receipt was sent to the sandbox env'),
}
Expand All @@ -21,14 +24,23 @@ class AppStoreValidator(object):
bundle_id = None
sandbox = None
url = None
auto_retry_wrong_env_request = False

def __init__(self, bundle_id, sandbox=False):
def __init__(self, bundle_id, sandbox=False, auto_retry_wrong_env_request=False):
""" Constructor for AppStoreValidator
:param bundle_id: apple bundle id
:param sandbox: sandbox mode ?
:param auto_retry_wrong_env_request: auto retry on wrong env ?
"""
self.bundle_id = bundle_id
self.sandbox = sandbox

if not self.bundle_id:
raise InAppPyValidationError('`bundle_id` cannot be empty')

self.auto_retry_wrong_env_request = auto_retry_wrong_env_request

self._change_url_by_sandbox()

def _change_url_by_sandbox(self):
Expand All @@ -38,21 +50,35 @@ def _change_url_by_sandbox(self):
def post_json(self, request_json):
self._change_url_by_sandbox()

return requests.post(self.url, json=request_json).json()
try:
return requests.post(self.url, json=request_json).json()
except (ValueError, RequestException):
raise InAppPyValidationError('HTTP error')

def validate(self, receipt, shared_secret=None):
""" Validates receipt against apple services.
:param receipt: receipt
:param shared_secret: optional shared secret.
:return: validation result or exception.
"""
receipt_json = {'receipt-data': receipt}

# if shared secret is provided, attach it as `password`.
if shared_secret:
receipt_json['password'] = shared_secret

try:
# Do a request.
api_response = self.post_json(receipt_json)
status = api_response['status']

# Check retry case.
if self.auto_retry_wrong_env_request and status in [21007, 21008]:
# switch environment
self.sandbox = not self.sandbox

api_response = self.post_json(receipt_json)
except (ValueError, RequestException):
raise InAppPyValidationError('HTTP error')
status = api_response['status']

status = api_response['status']
if status != api_result_ok:
error = api_result_errors.get(status, InAppPyValidationError('Unknown API status'))
error.raw_response = api_response
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

setup(
name='inapppy',
version='0.4',
version='0.5',
packages=['inapppy'],
install_requires=['rsa', 'requests'],
description="In-app purchase validation library for Apple AppStore and GooglePlay.",
Expand Down
26 changes: 26 additions & 0 deletions tests/test_appstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,29 @@ def test_appstore_validate_attach_raw_response_to_the_exception():
assert mock_method.call_args[0][0] == {'receipt-data': 'test-receipt', 'password': 'shared-secret'}
assert ex.raw_response is not None
assert ex.raw_response == raw_response


def test_appstore_auto_retry_wrong_env_request():
validator = AppStoreValidator(bundle_id='test-bundle-id', sandbox=False, auto_retry_wrong_env_request=True)
assert validator is not None
assert not validator.sandbox
assert validator.auto_retry_wrong_env_request

raw_response = {'status': 21007, 'foo': 'bar'}
with pytest.raises(InAppPyValidationError):
with patch.object(AppStoreValidator, 'post_json', return_value=raw_response) as mock_method:
validator.validate(receipt='test-receipt', shared_secret='shared-secret')
assert mock_method.call_count == 1
assert validator.url == 'https://buy.itunes.apple.com/verifyReceipt'
assert mock_method.call_args[0][0] == {'receipt-data': 'test-receipt', 'password': 'shared-secret'}
assert validator.sandbox is True

raw_response = {'status': 21008, 'foo': 'bar'}
with pytest.raises(InAppPyValidationError):
with patch.object(AppStoreValidator, 'post_json', return_value=raw_response) as mock_method:
validator.validate(receipt='test-receipt', shared_secret='shared-secret')
assert validator.sandbox is False
assert mock_method.call_count == 1
assert validator.url == 'https://buy.itunes.apple.com/verifyReceipt'
assert mock_method.call_args[0][0] == {'receipt-data': 'test-receipt', 'password': 'shared-secret'}

0 comments on commit 5cbdd00

Please sign in to comment.