
Security News
Browserslist-rs Gets Major Refactor, Cutting Binary Size by Over 1MB
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
|travis| |pypi| |downloads|
.. |travis| image:: https://travis-ci.org/dotpot/InAppPy.svg?branch=master :target: https://travis-ci.org/dotpot/InAppPy .. |pypi| image:: https://badge.fury.io/py/inapppy.svg :target: https://badge.fury.io/py/inapppy .. |downloads| image:: https://img.shields.io/pypi/dm/inapppy.svg :target: https://pypi.python.org/pypi/inapppy
Introduction
Installation
Google Play (receipt
+ signature
)
Google Play (verification)
Google Play (verification with result)
App Store (receipt
+ using optional shared-secret
)
App Store Response (validation_result
/ raw_response
) example
App Store, asyncio version (available in the inapppy.asyncio package)
Development
Donate
Introduction ===============
In-app purchase validation library for Apple AppStore
and GooglePlay
(App Store
validator have async support!). Works on python3.6+
Installation =============== ::
pip install inapppy
Google Play (validates receipt
against provided signature
using RSA)
===========================================================================
.. code:: python
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
.. code:: python
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": "XXXXXXXXX@XXXXXXXX.XXX",'
' "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
.. code:: python
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
Alternative to .verify
method, instead of raising an error result class will be returned.
.. code:: python
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
receipt
using optional shared-secret
against iTunes service).. code:: python
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
validation_result
/ raw_response
) example.. code:: json
{
"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"
}
.. code:: python
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
.. code:: bash
# run checks and tests
tox
# setup project
make setup
# check for lint errors
make lint
# run tests
make test
# run black
make black
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 | +------+--------------------------------------------+
FAQs
In-app purchase validation library for Apple AppStore and GooglePlay.
We found that inapppy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
Research
Security News
Eight new malicious Firefox extensions impersonate games, steal OAuth tokens, hijack sessions, and exploit browser permissions to spy on users.
Security News
The official Go SDK for the Model Context Protocol is in development, with a stable, production-ready release expected by August 2025.