At the recently held WWDC 2021, a new version of StoreKit 2 was presented. It is a framework that is responsible for making purchases in iOS. The share of apps with in-app purchases and subscriptions is growing steadily, and with the release of StoreKit 2, Apple has made it much easier to integrate purchases into the app. Today we will look at working with StoreKit 2 from the server side, that is, using the App Store Server API.
Authenticating Requests
In the current version of the API, a Shared Secret is required to send a request. This is a secret fixed string that can be obtained from the App Store Connect. The new version of the API uses the JSON Web Token (JWT) standard to authenticate requests.
Key generation
First of all, you need to create a private key with which the requests will be signed. This is done in the App Store Connect under Users and Access. There you need to go to the Keys tab and select the In-App Purchase key type. After creation, the key must be downloaded. You will also need its ID, which can be copied on the same page, and the Issuer ID, which is located on the App Store Connect API tab.
Token creation
JWT- App Store Server API
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}'
}
(signed transactions)
API JSON Web Signature (JWS). , .
Base64 .
Base64 .
.
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
, . alg , x5c — .
{
"kid": "AMP/DEV",
"alg": "ES256",
"x5c": [
"MIIEO...",
"MIIDK..."
]
}
appAccountToken
, . UUID . , (, .), , .
offerType
offerIdentifier
, , .offerType
:
1
— ( )
2
— ( )
3
—
(
2
3
),offerIdentifier
. , . .
inAppOwnershipType
, , . :
PURCHASED
FAMILY_SHARED
type
, . :
Auto-Renewable Subscription
Non-Consumable
Consumable
Non-Renewing Subscription
cancellation_date
cancellation_reason
revocationDate
revocationReason
. , , .
camelCase ( App Store Server API).
Unix timestamp .
{
"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"
}
GET https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}
, {originalTransactionId}
— . .
{
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"data": [
{
"subscriptionGroupIdentifier": "39636320",
"lastTransactions": [
{
"originalTransactionId": "1000000819078552",
"status": 2,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
]
}
]
}
status
, , , . :
1
— , .
2
— , .
3
— Billing Retry, , . Apple 60 . .
4
— Grace Period, , . App Store Connect Grace Period, .
5
— , .
signedTransactionInfo
, .
, . , , , . , .
{
"expirationIntent": 1,
"originalTransactionId": "1000000819078552",
"autoRenewProductId": "basic_subscription_1_month",
"productId": "basic_subscription_1_month",
"autoRenewStatus": 0,
"isInBillingRetryPeriod": false,
"signedDate": 1624520884048
}
, GET https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}
, {originalTransactionId}
— . , .
20 , , hasMore
true
. , GET- revision
, .
{
"revision": "1625872984000_1000000212854038",
"bundleId": "com.adapty.sample_app",
"environment": "Sandbox",
"hasMore": true,
"signedTransactions": [
"eyJraWQiOiJ...",
"joiRVMyNeyX...",
"5MnkvOTlOZl...",
...
]
}
, , . , .
(V1) , . , . , Apple : DID_CHANGE_RENEWAL_STATUS
INTERACTIVE_RENEWAL
, , - , , . (V2), , .
OFFER_REDEEMED
, EXPIRED
GRACE_PERIOD_EXPIRED
, . SUBSCRIBED
PRICE_INCREASE
— .
JWS.
, , , .
{
"notificationType": "SUBSCRIBED",
"subtype": "INITIAL_BUY",
"version": 2,
"data": {
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"appAppleId": 739104078,
"bundleVersion": 1,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
}
Sandbox
, , URL Sandbox : https://api.storekit-sandbox.itunes.apple.com
.
, , URL Production Sandbox . Sandbox V2, Production V1.
App Store Connect :
Sandbox , .
Sandbox .
Sandbox . , , 1 , 5 , .
Apple . , :
;
;
;
Sandbox .
API , receipt originalTransactionId. , .
, — . Adapty :