Giter Club home page Giter Club logo

inapppy's Introduction

InAppPy

travis pypi downloads

Table of contents

  1. Introduction
  2. Installation
  3. Google Play (receipt + signature)
  4. Google Play (verification)
  5. Google Play (verification with result)
  6. App Store (receipt + using optional shared-secret)
  7. App Store Response (validation_result / raw_response) example
  8. App Store, asyncio version (available in the inapppy.asyncio package)
  9. Development
  10. Donate

1. Introduction

In-app purchase validation library for Apple AppStore and GooglePlay (App Store validator have async support!). Works on python3.6+

2. Installation

pip install inapppy

3. Google Play (validates receipt against provided signature using RSA)

from inapppy import GooglePlayValidator, InAppPyValidationError


bundle_id = 'com.yourcompany.yourapp'
api_key = 'API key from the developer console'
validator = GooglePlayValidator(bundle_id, api_key)

try:
    # receipt means `androidData` in result of purchase
    # signature means `signatureAndroid` in result of purchase
    validation_result = validator.validate('receipt', 'signature')
except InAppPyValidationError:
    # handle validation error
    pass

An additional example showing how to authenticate using dict credentials instead of loading from a file

import json
from inapppy import GooglePlayValidator, InAppPyValidationError


bundle_id = 'com.yourcompany.yourapp'
# Avoid hard-coding credential data in your code. This is just an example. 
api_credentials = json.loads('{'
                             '   "type": "service_account",'
                             '   "project_id": "xxxxxxx",'
                             '   "private_key_id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",'
                             '   "private_key": "-----BEGIN PRIVATE KEY-----\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==\n-----END PRIVATE KEY-----\n",'
                             '   "client_email": "[email protected]",'
                             '   "client_id": "XXXXXXXXXXXXXXXXXX",'
                             '   "auth_uri": "https://accounts.google.com/o/oauth2/auth",'
                             '   "token_uri": "https://oauth2.googleapis.com/token",'
                             '   "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",'
                             '   "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/XXXXXXXXXXXXXXXXX.iam.gserviceaccount.com"'
                             ' }')
validator = GooglePlayValidator(bundle_id, api_credentials)

try:
    # receipt means `androidData` in result of purchase
    # signature means `signatureAndroid` in result of purchase
    validation_result = validator.validate('receipt', 'signature')
except InAppPyValidationError:
    # handle validation error
    pass

4. Google Play verification

from inapppy import GooglePlayVerifier, errors


def google_validator(receipt):
    """
    Accepts receipt, validates in Google.
    """
    purchase_token = receipt['purchaseToken']
    product_sku = receipt['productId']
    verifier = GooglePlayVerifier(
        GOOGLE_BUNDLE_ID,
        GOOGLE_SERVICE_ACCOUNT_KEY_FILE,
    )
    response = {'valid': False, 'transactions': []}
    try:
        result = verifier.verify(
            purchase_token,
            product_sku,
            is_subscription=True
        )
        response['valid'] = True
        response['transactions'].append(
            (result['orderId'], product_sku)
        )
    except errors.GoogleError as exc:
        logging.error('Purchase validation failed {}'.format(exc))
    return response

5. Google Play verification (with result)

Alternative to .verify method, instead of raising an error result class will be returned.

from inapppy import GooglePlayVerifier, errors


def google_validator(receipt):
    """
    Accepts receipt, validates in Google.
    """
    purchase_token = receipt['purchaseToken']
    product_sku = receipt['productId']
    verifier = GooglePlayVerifier(
        GOOGLE_BUNDLE_ID,
        GOOGLE_SERVICE_ACCOUNT_KEY_FILE,
    )
    response = {'valid': False, 'transactions': []}

    result = verifier.verify_with_result(
        purchase_token,
        product_sku,
        is_subscription=True
    )

    # result contains data
    raw_response = result.raw_response
    is_canceled = result.is_canceled
    is_expired = result.is_expired

    return result

6. App Store (validates receipt using optional shared-secret against iTunes service)

from inapppy import AppStoreValidator, InAppPyValidationError


bundle_id = 'com.yourcompany.yourapp'
auto_retry_wrong_env_request=False # if True, automatically query sandbox endpoint if
                                   # validation fails on production endpoint
validator = AppStoreValidator(bundle_id, auto_retry_wrong_env_request=auto_retry_wrong_env_request)

try:
    exclude_old_transactions=False # if True, include only the latest renewal transaction
    validation_result = validator.validate('receipt', 'optional-shared-secret', exclude_old_transactions=exclude_old_transactions)
except InAppPyValidationError as ex:
    # handle validation error
    response_from_apple = ex.raw_response  # contains actual response from AppStore service.
    pass

7. App Store Response (validation_result / raw_response) example

{
    "latest_receipt": "MIIbngYJKoZIhvcNAQcCoIIbj...",
    "status": 0,
    "receipt": {
        "download_id": 0,
        "receipt_creation_date_ms": "1486371475000",
        "application_version": "2",
        "app_item_id": 0,
        "receipt_creation_date": "2017-02-06 08:57:55 Etc/GMT",
        "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
        "request_date_pst": "2017-02-06 04:41:09 America/Los_Angeles",
        "original_application_version": "1.0",
        "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
        "request_date_ms": "1486384869996",
        "bundle_id": "com.yourcompany.yourapp",
        "request_date": "2017-02-06 12:41:09 Etc/GMT",
        "original_purchase_date_ms": "1375340400000",
        "in_app": [{
            "purchase_date_ms": "1486371474000",
            "web_order_line_item_id": "1000000034281189",
            "original_purchase_date_ms": "1486371475000",
            "original_purchase_date": "2017-02-06 08:57:55 Etc/GMT",
            "expires_date_pst": "2017-02-06 01:00:54 America/Los_Angeles",
            "original_purchase_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
            "purchase_date_pst": "2017-02-06 00:57:54 America/Los_Angeles",
            "expires_date_ms": "1486371654000",
            "expires_date": "2017-02-06 09:00:54 Etc/GMT",
            "original_transaction_id": "1000000271014363",
            "purchase_date": "2017-02-06 08:57:54 Etc/GMT",
            "quantity": "1",
            "is_trial_period": "false",
            "product_id": "com.yourcompany.yourapp",
            "transaction_id": "1000000271014363"
        }],
        "version_external_identifier": 0,
        "receipt_creation_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
        "adam_id": 0,
        "receipt_type": "ProductionSandbox"
    },
    "latest_receipt_info": [{
            "purchase_date_ms": "1486371474000",
            "web_order_line_item_id": "1000000034281189",
            "original_purchase_date_ms": "1486371475000",
            "original_purchase_date": "2017-02-06 08:57:55 Etc/GMT",
            "expires_date_pst": "2017-02-06 01:00:54 America/Los_Angeles",
            "original_purchase_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
            "purchase_date_pst": "2017-02-06 00:57:54 America/Los_Angeles",
            "expires_date_ms": "1486371654000",
            "expires_date": "2017-02-06 09:00:54 Etc/GMT",
            "original_transaction_id": "1000000271014363",
            "purchase_date": "2017-02-06 08:57:54 Etc/GMT",
            "quantity": "1",
            "is_trial_period": "true",
            "product_id": "com.yourcompany.yourapp",
            "transaction_id": "1000000271014363"
        }, {
            "purchase_date_ms": "1486371719000",
            "web_order_line_item_id": "1000000034281190",
            "original_purchase_date_ms": "1486371720000",
            "original_purchase_date": "2017-02-06 09:02:00 Etc/GMT",
            "expires_date_pst": "2017-02-06 01:06:59 America/Los_Angeles",
            "original_purchase_date_pst": "2017-02-06 01:02:00 America/Los_Angeles",
            "purchase_date_pst": "2017-02-06 01:01:59 America/Los_Angeles",
            "expires_date_ms": "1486372019000",
            "expires_date": "2017-02-06 09:06:59 Etc/GMT",
            "original_transaction_id": "1000000271014363",
            "purchase_date": "2017-02-06 09:01:59 Etc/GMT",
            "quantity": "1",
            "is_trial_period": "false",
            "product_id": "com.yourcompany.yourapp",
            "transaction_id": "1000000271016119"
        }],
    "environment": "Sandbox"
}

8. App Store, asyncio version (available in the inapppy.asyncio package)

from inapppy import InAppPyValidationError
from inapppy.asyncio import AppStoreValidator


bundle_id = 'com.yourcompany.yourapp'
auto_retry_wrong_env_request=False # if True, automatically query sandbox endpoint if
                                   # validation fails on production endpoint
validator = AppStoreValidator(bundle_id, auto_retry_wrong_env_request=auto_retry_wrong_env_request)

try:
    exclude_old_transactions=False # if True, include only the latest renewal transaction
    validation_result = await validator.validate('receipt', 'optional-shared-secret', exclude_old_transactions=exclude_old_transactions)
except InAppPyValidationError as ex:
    # handle validation error
    response_from_apple = ex.raw_response  # contains actual response from AppStore service.
    pass

9. Development

# run checks and tests
tox

# setup project
make setup

# check for lint errors
make lint

# run tests
make test

# run black
make black

10. Donate

You can support development of this project by buying me a coffee ;)

Coin Wallet
EUR https://paypal.me/LukasSalkauskas
DOGE DGjSG3T6g9h2k6iSku7mtKCynCpmwowpyN
BTC 1LZAiWmLYzZae4hq3ai9hFYD3e3qcwjDsU
ETH 0xD62245986345130edE10e4b545fF577Bd5BaE3E4

inapppy's People

Contributors

debox-dev avatar dependabot[bot] avatar dotpot avatar eturimike avatar german-asap avatar hannankamrantintash avatar iwoherka avatar j0nes2k avatar kevinlsk avatar m-r-epstein avatar natim avatar ruslux avatar schumannd avatar thaelathy avatar tinche avatar zironycho avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inapppy's Issues

Asyncio support

Hi,

I'm probably going to need something like this soon, so instead of writing my own perhaps I can contribute here?

I'm using asyncio though. Would you take a merge request adding another class in a separate package that does Apple verification using aiohttp?

AttributeError: 'InAppPyValidationError' object has no attribute 'raw_response'

Description

This error arose while using the recommended error handling from the readme:

except InAppPyValidationError as ex:
    response = ex.raw_response  # contains actual response from AppStore service.
    reporter.send_error_report(response)

Expected Behavior

Shouldn't fail

Actual Behavior

the line before the error gets reported to us failed

Context

this bug was caught by our automated error monitor.
In the breadcrumbs I found, that the URL "https://buy.itunes.apple.com/verifyReceipt" was queried twice:

It returned status code [503] Service Unavailable the first time.
The second time it returned [null].

There was also a JSONDecodeError: Expecting value: line 1 column 1 (char 0) somewhere. Maybe in the InAppPy packet? maybe a handling of a null value return needs to be added somewhere?
And maybe that is why raw_response was not set? It should still exist in the object though to avoid errors like this.

That's a lot of maybes, because unfortunately the bredcrumbs don't show exactly what code line was executed.

Your Environment

inapppy==2.3
Django==2.1
python-3.7.0

Authorize required using 2.4.3

When I apply the following code,

def subscription_verify_google(purchaseToken , sub_code):
	 

	bundle_id = 'gogoyuedu.book.job.news.learn.read'
	api_key = 'Alkene-608a20735120.json'
	validator = GooglePlayVerifier(bundle_id, api_key)

	try:
		validation_result = validator.verify(purchaseToken, sub_code ,is_subscription=True)
		print('validation_result')
		pprint(validation_result)

	except InAppPyValidationError:
		# handle validation error
		print('validation_error')
		pprint(InAppPyValidationError.raw_response)
		pass

it gives that the following GET request is sent

https://www.googleapis.com/androidpublisher/v3/applications/gogoyuedu.book.job.news.learn.read/purchases/subscriptions/WTmQVACJ%2BAYDH42ORur6MussL54zqaxDG0Rx3LN6TezJNf8z9Ju8tnxpUZ24vnZTcmpOz7POP%2Bs1FikxU%2BzNXScIZA5o3VMHUtfKUj5pV4GWJUYfRUnU7GeT0WKQOLX6fl51jqIkrW8hIbn9rJVDGKGfbBZfE8miF4F14yulPJZy8kZtDWlraXFY2DkUYlMtcJRt5aySpZ7quiUaAvQMDPk2hFYQiO1S4RD6BlULTtA9Rhwj4nCz6VWVzXtcD7BIl9KhRoSju6OXcPa9wDrIecTgJHdwNd9014HafBViXN8ImibeD%2BQ2fLy3ce8fXa5Vg4jxTh9XpEAmfPp%2BJVGFRA%3D%3D/tokens/pofojkmedaekkcgbnbffkhlk.AO-J1OzLqnRmivbLG_c0zn0GqdRa3mWLbDIPFHc5PFoX8t8IMQdbFVaoLBsmQGkjnAppizd8n1PA-GilxfrMcoNg4d8TyK1LA3bZrSNQnjenvtHMJpiM6oytuapojxGkklmHMZFh0C85YJo1LAeX2LPbccZ7CBRfRw?alt=json

Unfortunately, it gives the validation error and I find no clue for what Error it is. It goes to the exception and I cannot find the error behind when I use my test Android card. COuld you please suggested me what other steps do I miss ?

[FEATURE] Make Deprecation warning on AppStoreValidator conditional

Detailed Description

The AppStoreValidator __init__ method is emitting a deprecation warning in all cases currently.

Context

The deprecation warning is creating additional noise in test results for any project using AppStoreValidator even if the deprecated argument is not being used.
It's also triggering deprecated class warnings in my IDE.

Possible Implementation

        if bundle_id:
            warnings.warn("bundle_id will be removed in version 3, since it's not used here.",
                          PendingDeprecationWarning)

Happy to make a PR for this if it isn't objectionable but the PR template requests that an issue be created/discussed.

Your Environment

  • Version used: 2.5

Google subscription verification

You need to cast ms_timestamp to int in GooglePlayVerifier.verify(...), because _ms_timestamp_expired(ms_timestamp: int) will throw str to int cast exception. Please check it asap.

Some query about AppStoreValidator

Does this AppStoreValidator validate and verify the IAP receipt sent from the Apple? If so , Would you please mention where we can get the receiptData ?

bundle_id = 'com.yourcompany.yourapp'
auto_retry_wrong_env_request=False # if True, automatically query sandbox endpoint if
# validation fails on production endpoint
validator = AppStoreValidator(bundle_id, auto_retry_wrong_env_request=auto_retry_wrong_env_request)

try:
    exclude_old_transactions=False # if True, include only the latest renewal transaction
    validation_result = validator.validate('receipt', 'optional-shared-secret', exclude_old_transactions=exclude_old_transactions)
except InAppPyValidationError as ex:
    # handle validation error
    response_from_apple = ex.raw_response  # contains actual response from AppStore service.
    pass

Also, does the receipt data setup differ if I use sandbox environment / production environment ?

I see some uses base64 string of Bundle.main.appStoreURL for sandbox production
and hence purchase_token generated by Apple shall not be used at sandbox production

Error on _ms_timestamp_expired function (wrong time comparison)

Hi,

I tried to use the Google validation for subscriptions (padding is_subscription=True) and an issue.
On the _ms_timestamp_expired the date comparison is wrong since it compares datetime.datetime.fromtimestamp with now.

return datetime.datetime.fromtimestamp(ms_timestamp_value) < now

One value is the local time of the server (with timezone) and the other one is in UTC.
When the current time and the expiration date are too close, you can get false negative values.

I think it should be fixed with
return datetime.datetime.utcfromtimestamp(ms_timestamp_value) < now

GooglePlayValidator - unhashable type: 'slice'

First off I have never done IAP receipt validation so have been searching for the least stressful method of implementation.

I believe my key is in the correct format but seem to be having issues

Description

I've read in an api key that is in the same format as the example.
In my example its read in as a dictionary. I've also tried to read it in as a string just in case but still throws an error.

Also running the example throws a bug

Actual Behavior

validator = GooglePlayValidator(bundle_id, api_credentials)
File "C:\Users\Marc\Desktop\Purchase Validation\venv\lib\site-packages\inapppy\googleplay.py", line 45, in init
pem = make_pem(api_key)
File "C:\Users\Marc\Desktop\Purchase Validation\venv\lib\site-packages\inapppy\googleplay.py", line 18, in make_pem
return "\n".join(("-----BEGIN PUBLIC KEY-----", "\n".join(value), "-----END PUBLIC KEY-----"))
File "C:\Users\Marc\Desktop\Purchase Validation\venv\lib\site-packages\inapppy\googleplay.py", line 17, in
value = (public_key[i : i + 64] for i in range(0, len(public_key), 64)) # noqa: E203
TypeError: unhashable type: 'slice'

Possible Fix

Steps to Reproduce

import json
from inapppy import GooglePlayValidator, InAppPyValidationError

bundle_id = 'com.yourcompany.yourapp'

Avoid hard-coding credential data in your code. This is just an example.

with open("service_account.json", "r") as content:
api_credentials = json.load(content)

validator = GooglePlayValidator(bundle_id, api_credentials)

try:
# receipt means androidData in result of purchase
# signature means signatureAndroid in result of purchase
validation_result = validator.validate('receipt', 'signature')
except InAppPyValidationError:
# handle validation error
pass

Context

Your Environment

  • Version used: 2.5.2
  • Operating System and version: Windows 10

how to Authorize before verifying the purchase ?

When I practice the module to verify my Google subscription purchase , it turns out to be :

Would you please tell me which module / attribute of GooglePlayVerifier to authorise ?

Here is my code :

bundle_id = 'gogoyuedu.book.job.news.learn.read'
			api_key = 'Alkene-608a20735120.json'
			validator = GooglePlayVerifier(bundle_id, api_key)
			response = {'valid': False, 'transactions': []}
			try:
		# receipt means `androidData` in result of purchase
		# signature means `signatureAndroid` in result of purchase
				validation_result = validator.verify(purchase_json ,sub_code ,is_subscription=True )
				print("validation_result:")
				pprint(validation_result)
	
				response['valid'] = True
				response['transactions'].append((result['orderId'], product_sku))

			except errors.GoogleError as exc:
				logging.error('Purchase validation failed {}'.format(exc))
			return response

GooglePlayVerifier class timestamp conversion from string to int

Getting this issue while verifying the subscription. It is dividing a string by an integer. Error is occurring on Line 84

Please replace this line with:

ms_timestamp_value = int(ms_timestamp) / 1000

This will fix this issue. I'm making a pull request for this. Please verify and merge it ASAP. Thanks.

Validating Google Play Receipt

Google has an api to validate receipt. In InAppPy, for Google play receipt validation, no API call is used. Is it valid method or we should use API for google play case?

validate_signature(googleplay) Unable to accept purchase Data signature

validate_signature(googleplay) Unable to accept purchase Data signature

I am trying to verify the product purchased from google play with the Google Play (receipt + signature) method, but gooleplay.py _validate_signature function does not accept the "signature" transmitted by google play by warning "Bad signature", am I missing something? Is my definition of "sig = base64.standard_b64decode(signature)" wrong? I do not understand.

[FEATURE]Could you extend your library to InApp purchases, at least for Google Play?

Detailed Description

Right now you library can validate Google Play and AppStore purchases with receipts, signatures etc. There is only one step left to make it complete InApp purchases python library - to add possibility to make purchases.

Context

It would be very good to have supported and constantly developing all-in-one InApp purchases library.

readme improvements

Current read-me does not cover few important sections:

  • how-to set-up project for development (requirements/tests).
  • contribution "rules".
  • other (feel free to post your ideas in the comments).

Incorrect padding

Here is my purchase json created by my Android Test card to validate my receipt. When my cloud function code comes to the implementation and testing, the result tells me
Error occurred processing request and hence Incorrect padding at the line :
validator = GooglePlayValidator(bundle_id, api_key)

Should my api_key copied from my google play developer console to be encrypted?

CODE:

validator = GooglePlayValidator(bundle_id, api_key)
	validation_result = validator.validate( receipt , signature)
	print("validation_result")    
	pprint(validation_result)
	return validation_result

[BUG] Async example wrong

Seems like session for validator creates only on __aenter__

File "inapppy/asyncio/appstore.py", line 47, in validate
    api_response = await self.post_json(receipt_json)
  File "inapppy/asyncio/appstore.py", line 30, in post_json
    async with self._session.post(
AttributeError: 'NoneType' object has no attribute 'post'

Working example for me

async with validator:
    validation_result = await validator.validate(...)

Unify handling of valid responses across apple and google

Currently a valid google response stating e.g. that a subscription has run out causes this package to throw an exception.

The same situation with apple however does not, and the parsing of the information needs to be done by the user.

I think the API should react similarily for both cases, either throw an error for valid, expired responses or not.

What do you think?

cancelled android purchases do not get caught correctly

in lines 109-110 of googleplay.py:

            cancel_reason = int(result.get('cancelReason', 0))
            if cancel_reason != 0:
                raise GoogleError('Subscription is canceled', result)

If we look at the docs we see that a value of cancelReason = 0 also indicates cancellation by user. Therefore I do not understand what was the original intention of this snippet of code. If we look at the lines following this snippet:

            ms_timestamp = result.get('expiryTimeMillis', 0)
            if self._ms_timestamp_expired(ms_timestamp):
                raise GoogleError('Subscription expired', result)
        else:
            result = self.check_purchase_product(purchase_token, product_sku, service)
            purchase_state = int(result.get('purchaseState', 1))

            if purchase_state != 0:
                raise GoogleError('Purchase cancelled', result)

I think the general issue here is, that a subscription can be

a) not cancelled and not expired
b) cancelled and not expired
c) cancelled and expired

So even if we fixed the issue with the code above, we would have to rethink how we throw errors / catch google certificate expiry (see also #25).

I suggest we remove the current mechanism of throwing errors an instead return some sort of datastructure containing: raw_response, is_expired, is_cancelled.

That way we describe all 3 scenarios above. What do you think?

GoogleVerificationResult: how to interpret `is_canceled` and separate different cases?

Description

There is a method in GooglePlayVerifier class:

    def verify_with_result(
        self, purchase_token: str, product_sku: str, is_subscription: bool = False
    ) -> GoogleVerificationResult:
        """Verifies by returning verification result instead of raising an error,
        basically it's and better alternative to verify method."""
        service = build("androidpublisher", "v3", http=self.http)
        verification_result = GoogleVerificationResult({}, False, False)

        if is_subscription:
            result = self.check_purchase_subscription(purchase_token, product_sku, service)
            verification_result.raw_response = result

            cancel_reason = int(result.get("cancelReason", 0))
            if cancel_reason != 0:
                verification_result.is_canceled = True

            ms_timestamp = result.get("expiryTimeMillis", 0)
            if self._ms_timestamp_expired(ms_timestamp):
                verification_result.is_expired = True
        else:
            result = self.check_purchase_product(purchase_token, product_sku, service)
            verification_result.raw_response = result

            purchase_state = int(result.get("purchaseState", 1))
            if purchase_state != 0:
                verification_result.is_canceled = True

        return verification_result

According to publisher docs, Google uses code 0 as a valid code of cancellation in property cancelReason. It means, that this code indicates, that subscription was canceled (by user). On the other hand, code 0 in this method used as a value of variable, which means, that the subscription is not canceled.

The question is, how to interpret GoogleVerificationResult.is_canceled (what does this attribute mean)? And how to separate the cases, when:

  • subscription was not cancelled
  • subscription canceled by user? (with code 0)

Expected Behavior

is_canceled means, that subscription is canceled (by user or not)

Actual Behavior

is_canceled means, that subscription was canceled, but not by user

Possible Fix

It depends on the meaning of the is_canceled attribute. Maybe some comment would be useful, if everything works as expected. Or, If it is a bug, then as a possible fix: change the condition check (don't use value 0 as indicator, because this value is reserved by Google).

Your Environment

  • Version used: 2.4.4
  • Operating System and version: Docker python:3.7.4-buster

ex.raw_response throws exception when apple server is unavailable

Happen to me a couple of weeks ago, request returned 503 code.

Exception:

AttributeError: 'InAppPyValidationError' object has no attribute 'raw_response'

I think just fixing docs to do a None type check before accessing should be sufficient here.

I just want to share this info as apple's in-app purchase servers have a tendency to go south from time to time.

Permissions issue

How can I properly setup the google service account? It seems like there are permissions missing. I tried to find out in google but it's very complicated and depressing for somebody who is doing this for the first time. It would be great if you could include a small tutorial.

result = subscriptions_get.execute(http=self.http)

File "/home//.local/lib/python3.10/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
File "/home//.local/lib/python3.10/site-packages/googleapiclient/http.py", line 938, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 401 when requesting https://androidpublisher.googleapis.com/androidpublisher/v3/applications/..../purchases/subscriptions/coins1234/tokens/....?alt=json returned "The current user has insufficient permissions to perform the requested operation.". Details: "[{'message': 'The current user has insufficient permissions to perform the requested operation.', 'domain': 'androidpublisher', 'reason': 'permissionDenied'}]">

InAppPyValidationError doesn't return message when error occurs in App Store

When an exception is raised at appstore.py line 86, the App Store error messages declared at the top of the file are not included. Only raw_response parameter is set in the exception object.

I think instead of storing raw_response parameter inside InAppPyValidationError's constructor you should set that value to the message, and set raw_response only in the aforementioned line..

[BUG] New subscription (for google) need the v2 endpoint to verify them

Subscriptions can soon no longer be validated with the original subscription link, but should go to the v2 version.

https://developer.android.com/google/play/billing/compatibility

Description

I tried a long time to get things working with the current code, but google was giving me cryptic error message saying I provided wrong arguments to the API call when calling the URL:
Method: https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptions/{subscriptionId}/tokens/{token}

Expected Behavior

I would have expected it to say something about the subscription.

Actual Behavior

I always received : no matter what I tried
Error Response: { "error": { "code": 400, "message": "Your request is invalid for this subscription purchase.", "errors": [ { "message": "Your request is invalid for this subscription purchase.", "domain": "androidpublisher", "reason": "subscriptionInvalidArgument" } ] } }

Possible Fix

Looking at the following link, I noticed there is now a subscriptionv2 endpoint that should be used.
https://developer.android.com/google/play/billing/compatibility

Also present in the androidpublisher.v3.json file:
"subscriptionsv2": {
"methods": {
"get": {
"description": "Get metadata about a subscription",
"flatPath": "androidpublisher/v3/applications/{packageName}/purchases/subscriptionsv2/tokens/{token}",
"httpMethod": "GET",
"id": "androidpublisher.purchases.subscriptionsv2.get",
"parameterOrder": [
"packageName",
"token"
],
"parameters": {
"packageName": {
"description": "The package of the application for which this subscription was purchased (for example, 'com.some.thing').",
"location": "path",
"required": true,
"type": "string"
},
"token": {
"description": "Required. The token provided to the user's device when the subscription was purchased.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "androidpublisher/v3/applications/{packageName}/purchases/subscriptionsv2/tokens/{token}",
"response": {
"$ref": "SubscriptionPurchaseV2"
},
"scopes": [
"https://www.googleapis.com/auth/androidpublisher"
]
}
}
}

Steps to Reproduce

When using the new v2 framework in googleplay.py, it seems to work again:

    def check_purchase_subscription(self, purchase_token: str, product_sku: str, service) -> dict:
        try:
            purchases = service.purchases()
            subscriptions = purchases.subscriptionsv2()
            subscriptions_get = subscriptions.get(
                packageName=self.bundle_id,  token=purchase_token
            )
            result = subscriptions_get.execute(http=self.http)
            return result
        except HttpError as e:
            if e.resp.status == 400:
                raise GoogleError(e.resp.reason, repr(e))
            else:
                raise e

Your Environment

  • Version used: 2.5.2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.