Le novità dell’API StoreKit 2 e come Apple ha semplificato l’integrazione degli acquisti in-app
Updated: Marzo 20, 2023
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.
Richiesta di autenticazione
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.
Generazione della chiave
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.
Creazione di un token
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}'
}
Transazioni firmate
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.
- Intestazione Base64.
- Payload della transazione Base64
- Firma della transazione.
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
Intestazione della transazione
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..."
]
}
Payload della transazione
{
"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.
- Apple ha aggiunto il campo appAccountToken, che contiene l’ID utente del sistema. Questo ID deve essere in formato UUID e viene impostato nell’applicazione mobile quando viene inizializzato un acquisto. Se definito, verrà restituito in tutte le transazioni di questa catena (rinnovi (renewals), problemi di fatturazione (billing issues), ecc.), e potrai capire facilmente quale utente abbia effettuato l’acquisto.
- Apple ha anche aggiunto i campi offerType e offerIdentifier che contengono le informazioni su un’offerta utilizzata (se presente). Di seguito, i valori del campo offerType:
- 1 — offerta introduttiva (intro offer) (disponibile solo per gli utenti senza abbonamenti attivi o scaduti)
- 2 — offerta promozionale (promo offer) (disponibile solo per gli abbonamenti in corso e scaduti)
- 3 — codice dell’offerta
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.
- Apple ha aggiunto il campo inAppOwnershipType, che aiuta a capire se un utente abbia acquistato un prodotto (product) o vi abbia avuto accesso grazie a un abbonamento familiare. Valori possibili:
- PURCHASED
- FAMILY_SHARED
- Un altro nuovo campo – type – include il tipo di transazione. Valori possibili:
- Auto-Renewable Subscription
- Non-Consumable
- Consumable
- Non-Renewing Subscription
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.
- Tutte le chiavi vengono restituite in formato camelCase (come in tutte le richieste API del server dell’App Store).
- Tutte le date sono visualizzate nel formato Unix timestamp in millisecondi.
Stato dell’abbonamento dell’utente
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:
- 1 — l’abbonamento è attivo, l’utente deve essere in grado di accedere alle funzioni a pagamento.
- 2 — l’abbonamento è scaduto, l’utente non deve poter accedere alle funzioni a pagamento.
- 3 —lo stato dell’abbonamento è Nuovo tentativo di fatturazione (Billing Retry), cioè l’utente non ha annullato l’abbonamento, ma ha avuto problemi di pagamento. Apple ritenterà l’addebito sulla carta per 60 giorni. l’utente non deve poter accedere alle funzioni a pagamento).
- 4 — lo stato dell’abbonamento è Periodo di tolleranza (Grace Period), cioè l’utente non ha annullato l’abbonamento, ma ha avuto problemi di pagamento. Il periodo di tolleranza è attivo in App Store Connect, quindi l’utente deve poter accedere alle funzioni a pagamento.
- 5 — l’abbonamento è stato annullato in seguito a un rimborso (refund), l’utente non deve poter accedere alle funzioni a pagamento.
La chiave SignedTransactionInfo contiene le informazioni sull’ultima transazione della catena. I dettagli sul suo formato sono riportati sopra.
Informazioni sul rinnovo dell’abbonamento (subscription renewal)
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.
Cronologia delle transazioni utente
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.
Notifiche delle transazioni del server
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.
Tipi di notifica
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.
Il lavoro nell’ambiente Sandbox
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:
- Cancellare la cronologia degli acquisti per l’utente Sandbox, il che significa che non è più necessario creare un nuovo account per farlo.
- Modificare il paese del negozio per l’utente Sandbox.
- Modificare il periodo di rinnovo dell’abbonamento a Sandbox, ad esempio è possibile effettuare un acquisto mensile della durata di 1 ora anziché di 5 minuti.
Conclusione
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:
- Supporto completo per offerte promozionali e codici offerta;
- Notifiche del server più semplici e informative;
- La possibilità di conoscere lo stato dell’abbonamento corrente con una semplice chiamata API;
- La cancellazione della cronologia degli acquisti nel Sandbox dell’utente.
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:
- Le analisi integrate ci permettono di capire rapidamente i parametri dell’app principale.
- L’analisi di coorte (Cohort analysis) aiuta a capire se si verificano problemi di economia.
- Il test A/B (A/B testing) accresce la redditività dell’app.
- Le integrazioni con sistemi esterni consentono di inviare le transazioni a servizi di attribuzione e analisi dei prodotti.
- Le campagne promozionali riducono la perdita di pubblico.
- L’SDK open-source permette di integrare gli abbonamenti in un’app in poche ore.
Scopri di più su queste funzionalità, per implementare più velocemente gli abbonamenti nella tua app e monetizzarla prima e meglio.