En la reciente WWDC 2021 se presentó una nueva versión de StoreKit 2. Es un framework que se encarga de realizar compras en iOS. La proporción de aplicaciones con suscripciones y compras dentro de la aplicación está creciendo de manera constante, y con el lanzamiento de StoreKit 2, Apple ha facilitado mucho la integración de compras en la aplicación. Hoy veremos cómo trabajar con StoreKit 2 desde el lado del servidor, es decir, usando la API de App Store Server.
Autenticar solicitudes
En la versión actual de la API, se requiere un secreto compartido para enviar una solicitud. Esta es una cadena fija secreta que se puede obtener en App Store Connect. La nueva versión de la API utiliza el estándar JSON Web Token (JWT) para autenticar solicitudes.
Generación de claves
En primer lugar, debe crear una clave privada con la que se firmarán las solicitudes. Esto se hace en App Store Connect en Users and Access. Allí debe ir a la pestaña Claves y seleccionar el tipo de clave Compra en la aplicación. Después de la creación, la clave debe descargarse. También necesitará su ID, que se puede copiar en la misma página, y el ID del emisor, que se encuentra en la pestaña App Store Connect API.
Creación de tokens
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 :