106
loading...
This website collects cookies to deliver better user experience
import time, uuid
from authlib.jose import jwt
BUNDLE_ID = 'com.adapty.sample_app'
ISSUER_ID = '4336a124-f214-4d40-883b-6db275b5e4aa'
KEY_ID = 'J65UYBDA74'
PRIVATE_KEY = '''
-----BEGIN PRIVATE KEY-----
MIGTAgMGByqGSMBHkAQQgR/fR+3Lkg4...
-----END PRIVATE KEY-----
'''
issue_time = round(time.time())
expiration_time = issue_time + 60 * 60 # 1 hour expiration
header = {
'alg': 'ES256',
'kid': KEY_ID,
'typ': 'JWT'
}
payload = {
'iss': ISSUER_ID,
'iat': issue_time,
'exp': expiration_time,
'aud': 'appstoreconnect-v1',
'nonce': str(uuid.uuid4()),
'bid': BUNDLE_ID
}
token_encoded = jwt.encode(header, payload, PRIVATE_KEY)
token_decoded = token_encoded.decode()
authorization_header = {
'Authorization': f'Bearer {token_decoded}'
}
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
Alg
key contains an encryption algorithm, x5c
key contains a certificate chain.{
"kid": "AMP/DEV",
"alg": "ES256",
"x5c": [
"MIIEO...",
"MIIDK..."
]
}
{
"transactionId": "1000000831360853",
"originalTransactionId": "1000000806937552",
"webOrderLineItemId": "1000000063561721",
"bundleId": "com.adapty.sample_app",
"productId": "basic_subscription_1_month",
"subscriptionGroupIdentifier": "27636320",
"purchaseDate": 1624446341000,
"originalPurchaseDate": 1619686337000,
"expiresDate": 1624446641000,
"quantity": 1,
"type": "Auto-Renewable Subscription",
"appAccountToken": "fd12746f-2d3a-46c8-bff8-55b75ed06aca",
"inAppOwnershipType": "PURCHASED",
"signedDate": 1624446484882,
"offerType": 2,
"offerIdentifier": "basic_subscription_1_month.pay_as_you_go.3_months"
}
Apple added appAccountToken
field, which contains your system’s user ID. This ID must be in UUID format, it is set in the mobile app when a purchase is being initialized. If it is set, it will be returned in all transactions in this chain (renewal, billing issues, etc.), and you will easily understand which user made a purchase.
Apple also added offerType
and offerIdentifier
fields that contain the information about a used offer (if any). Here are values for offerType field:
1 — intro offer (available only for the users without active or expired subscriptions);
2 — promo offer (available only for current and expired subscriptions);
3 — offer code. If a promo offer or offer code was used, offerIdentifier
key will contain the ID of the used offer. In the past, it was impossible to track the use of the offer on the server-side, this worsened the analytics. Now, you can use offer codes for analytics.
Apple added inAppOwnershipType
field, which helps to understand whether a user bought a product or accessed it thanks to a family subscription. Possible values:
PURCHASED
FAMILY_SHARED
Another new field – type
– includes transaction type. Possible values:
Auto-Renewable Subscription
Non-Consumable
Consumable
Non-Renewing Subscription
Cancellation_date
and cancellation_reason
fields have new names now: revocationDate
and revocationReason
. As a reminder, they contain a date and a reason for subscription revocation as a result of a refund, so the new name looks more logical.
All keys return in camelCase format (just like in all App Store Server API requests).
All dates are displayed in Unix timestamp format in milliseconds.
https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}
, where {originalTransactionId}
is the ID of any transaction chain of the user. In return, you will get transactions with statuses for every group of subscriptions.{
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"data": [
{
"subscriptionGroupIdentifier": "39636320",
"lastTransactions": [
{
"originalTransactionId": "1000000819078552",
"status": 2,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
]
}
]
}
status
key displays the current subscription status, based on it, you can decide if you should provide a user with access to the paid features of the app. Possible values:{
"expirationIntent": 1,
"originalTransactionId": "1000000819078552",
"autoRenewProductId": "basic_subscription_1_month",
"productId": "basic_subscription_1_month",
"autoRenewStatus": 0,
"isInBillingRetryPeriod": false,
"signedDate": 1624520884048
}
https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}
, where {originalTransactionId}
is the ID of any chain of transactions of the user. In return, you will get an array of transactions sorted by time.{
"revision": "1625872984000_1000000212854038",
"bundleId": "com.adapty.sample_app",
"environment": "Sandbox",
"hasMore": true,
"signedTransactions": [
"eyJraWQiOiJ...",
"joiRVMyNeyX...",
"5MnkvOTlOZl...",
...
]
}
hasMore
flag’s value will be true
. If you need the next transaction page, send the request again with the revision
GET parameter containing. It will contain the value from the same key. DID_CHANGE_RENEWAL_STATUS
and INTERACTIVE_RENEWAL
. To process this case currently, you need to save the status somehow and check if the second notification was sent. In a new version of server notifications (V2), there’s only one notification for one action of a user. This is much more convenient.OFFER_REDEEMED
, EXPIRED
, and GRACE_PERIOD_EXPIRED
. They make managing subscriber status much easier. SUBSCRIBED
and PRICE_INCREASE
events are improved events from the first version.{
"notificationType": "SUBSCRIBED",
"subtype": "INITIAL_BUY",
"version": 2,
"data": {
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"appAppleId": 739104078,
"bundleVersion": 1,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
}
https://api.storekit-sandbox.itunes.apple.com.
originalTransactionId
for every receipt. It’s likely it’s already contained in your base.