
Tutorial
September 6, 2022
18 min read
September 6, 2022
22 min read
Durante la WWDC 2021 che ha avuto luogo di recente, Apple ha presentato una nuova versione di StoreKit 2. Si tratta di un framework responsabile dell’elaborazione degli acquisti in iOS. La quota di app con funzioni di acquisto in-app (in-app purchase) e di abbonamento cresce costantemente e con il rilascio di StoreKit 2, Apple ha semplificato notevolmente l’integrazione (integration) degli acquisti in-app nell’app. Oggi prenderemo in considerazione la possibilità di lavorare con StoreKit 2 dal lato del server, in altre parole, con l’aiuto dell’API del server dell’App Store Server.
Nella versione attuale dell’API, per inviare una richiesta è necessario il segreto condiviso (shared secret). Si tratta di una stringa fissa segreta che si può ottenere in App Store Connect. Una nuova versione dell’API utilizza lo standard JSON Web Token (JWT) per l’autenticazione delle richieste.
Innanzitutto, crea una chiave privata che verrà usata per autorizzare le richieste. Apri App Store Connect e vai alla sezione Users e Access, quindi alla scheda Keys. Seleziona il tipo di chiave per l’acquisto in-app. Scarica una nuova chiave. sarà necessario anche il suo ID, che può essere copiato nella stessa pagina come Issue ID e che si trova nella scheda API di App Store Connect.
Il passo successivo è la creazione di un token che verrà utilizzato per autorizzare le richieste. Questo processo viene descritto dettagliatamente nella documentazione, quindi non è necessario approfondirlo qui. Ecco un esempio di implementazione (implementation) già pronta per Python. Vale la pena notare che non ha senso generare un nuovo token per ogni nuova richiesta. Quando si crea un token, si imposta la sua durata fino a 60 minuti e si utilizza lo stesso token nell’arco di tutto questo periodo.
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}'
}
Nella nuova versione dell’API, tutte le transazioni vengono restituite in standard JSON Web Signature (JWS). Si tratta di una stringa composta da tre parti divise da punti.
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
L’intestazione è necessaria per garantire l’autenticità della transazione. La chiave Alg contiene un algoritmo di crittografia, quella x5c contiene una catena di certificati.
{
"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 ha modificato ed esteso il formato delle transazioni. Dal mio punto di vista, ora è più conveniente lavorarci. I dettagli dei nuovi formati sono disponibili nella documentazione. Di seguito, ne descriverò le modifiche più importanti.
Se sono stati utilizzati un’offerta promozionale o un codice offerta, la chiave offerIdentifier conterrà l’ID dell’offerta utilizzata. In passato, era impossibile tracciare l’utilizzo dell’offerta sul lato server (server-side), il che aveva come risultato un’analisi qualitativamente peggiore. Ora, per l’analisi, è possibile utilizzare i codici offerta.
I campi Cancellation_date e Cancellation_reason hanno ora nuovi nomi: revocationDate e revocationReason. Come promemoria, contengono la data e il motivo di revoca dell’abbonamento (subscription revocation) a seguito di un rimborso, quindi il nuovo nome appare più logico.
Per verificare l’attuale stato dell’abbonamento di un utente, invia una richiesta GET a https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}, dove {originalTransactionId} è l’ID di qualsiasi catena di transazioni dell’utente. Come risultato, verranno restituite le transazioni con lo stato per ciascun gruppo di abbonamenti.
{
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"data": [
{
"subscriptionGroupIdentifier": "39636320",
"lastTransactions": [
{
"originalTransactionId": "1000000819078552",
"status": 2,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
]
}
]
}
La chiave dello stato mostra lo stato attuale dell’abbonamento, in base al quale si può decidere se fornire a un utente l’accesso alle funzioni a pagamento dell’app. Valori possibili:
La chiave SignedTransactionInfo contiene le informazioni sull’ultima transazione della catena. I dettagli sul suo formato sono riportati sopra.
La chiave SignedRenewalInfo contiene le informazioni sul rinnovo dell’abbonamento.
{
"expirationIntent": 1,
"originalTransactionId": "1000000819078552",
"autoRenewProductId": "basic_subscription_1_month",
"productId": "basic_subscription_1_month",
"autoRenewStatus": 0,
"isInBillingRetryPeriod": false,
"signedDate": 1624520884048
}
Queste informazioni ci permettono di capire cosa succederà all’abbonamento durante il prossimo periodo di pagamento. Ad esempio, se constati che un utente ha annullato il rinnovo automatico, potresti proporgli un altro piano di abbonamento o un’offerta promozionale . È comodo tenere traccia di questo tipo di eventi con l’aiuto delle notifiche del server (server notifications), delle quali ti parlerò tra poco.
Per verificare la cronologia delle transazioni di un utente, invia una richiesta GET a https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}, dove {originalTransactionId} è l’ID di qualsiasi catena di transazioni dell’utente. Come risultato, verrà restituito un array di transazioni ordinate per tempo.
{
"revision": "1625872984000_1000000212854038",
"bundleId": "com.adapty.sample_app",
"environment": "Sandbox",
"hasMore": true,
"signedTransactions": [
"eyJraWQiOiJ...",
"joiRVMyNeyX...",
"5MnkvOTlOZl...",
...
]
}
Una richiesta non può contenere più di 20 transazioni. Se un utente ne ha di più, il valore del flag hasMore sarà “true”. Se hai bisogno della pagina della transazione successiva, invia nuovamente la richiesta con il parametro GET di revisione contenente. Conterrà il valore dalla stessa chiave.
Le notifiche del server aiutano a ottenere informazioni su nuovi acquisti, rinnovi, problemi di fatturazione, ecc. Questo aiuta a concepire analisi più accurate e a semplificare la gestione dello stato degli abbonati.
Le notifiche del server esistenti (V1) possono risolvere la maggior parte dei problemi, ma a volte sono scomode. Si tratta soprattutto della situazione in cui si ricevono diverse notifiche per una sola azione di un utente. Ad esempio, ora, quando un utente aggiorna un abbonamento, Apple invia due notifiche: DID_CHANGE_RENEWAL_STATUS e INTERACTIVE_RENEWAL. Attualmente, per elaborare questo caso, è necessario salvare lo stato in qualche modo e verificare se la seconda notifica sia stata inviata. Nella nuova versione delle notifiche del server (V2), c’è solo una notifica per un’azione di un utente. Quest’opzione è molto più comoda.
La seconda versione delle notifiche del server presenta nuovi eventi: OFFER_REDEEMED, EXPIRED, e GRACE_PERIOD_EXPIRED. Con questi, la gestione dello stato degli abbonati è molto più semplice. Gli eventi SUBSCRIBED e PRICE_INCREASE sono stati migliorati, rispetto alla prima versione.
Le notifiche ora hanno un “tipo”, quindi una notifica per qualsiasi azione di un utente è sufficiente per capire cosa sia successo.
Tipi di notifica
{
"notificationType": "SUBSCRIBED",
"subtype": "INITIAL_BUY",
"version": 2,
"data": {
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"appAppleId": 739104078,
"bundleVersion": 1,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
}
Le notifiche del server contengono informazioni su una transazione e il rinnovo nel formato JWS descritto in precedenza.
Per testare gli acquisti, è necessario utilizzare l’URL dell’ambiente Sandbox: https://api.storekit-sandbox.itunes.apple.com.
Non è ancora disponibile una nuova versione di notifica dal server. Una volta disponibile, sarà possibile specificare URL diversi per le notifiche di Produzione e Sandbox. È possibile scegliere V2 per Sandbox e V1 per Production per il test.
Inoltre, App Store Connect consente ora di:
Apple ha migliorato in modo significativo il funzionamento degli acquisti in-app e degli abbonamenti sul lato server. Dal mio punto di vista, le novità più utili sono queste:
Passare a una nuova API non sarà difficile, è sufficiente ottenere originalTransactionId per ogni ricevuta. È probabilmente già contenuta nel tuo database.
In ogni caso, la parte più difficile dell’integrazione degli abbonamenti in un’app mobile è la costruzione di un sistema di analisi e l’ottimizzazione dell’economia. Adapty risolve questi problemi efficacemente:
Scopri di più su queste funzionalità, per implementare più velocemente gli abbonamenti nella tua app e monetizzarla prima e meglio.
Further reading
Tutorial
September 6, 2022
18 min read
Tutorial
September 6, 2022
36 min read
Tutorial
September 6, 2022
22 min read