
Tendenze-insight
Settembre 6, 2022
Updated: Marzo 20, 2023
La convalida lato server (convalida delle ricevute (receipt validation) lato server) è un modo per verificare l’autenticità dell’acquisto. A differenza della convalida basata sul dispositivo, quella del server si verifica, guarda un po’, sul lato server. “Convalida” significa che il dispositivo o il server effettua una richiesta ai server di Apple per sapere se l’acquisto sia effettivamente avvenuto e se sia valido.
Va notato che la convalida lato server non è obbligatoria: gli acquisti in-app continueranno a funzionare anche senza. Tuttavia, assicura più di qualche vantaggio:
In base alla nostra esperienza, il primo vantaggio è già sufficiente per impostare l’elaborazione degli acquisti lato server.
In generale, il processo di convalida delle ricevute su iOS è il seguente:
Per inviare una richiesta di convalida del pagamento, è necessario includere il segreto condiviso, per autorizzare la richiesta. Può essere generato in App Store Connect.
Il segreto condiviso può essere creato per un’app specifica (segreto specifico dell’app) o per tutte le app dell’account (segreto primario).
Per generare un segreto specifico per l’app, apri la pagina dell’app in App Store Connect, vai su In-App Purchases → Manage e fai clic su App-Specific Shared Secret. Nella finestra che si aprirà, potrai generare un nuovo token o copiare quello esistente.
Per ricevere il segreto per tutte le app dell’account, apri la pagina Users e Access e seleziona la scheda Shared Secret.
Una volta ricevuto il segreto condiviso, potrai inviare le ricevute per farle convalidare sui server Apple. Questo viene fatto tramite la richiesta verifyReceipt. Devi inviare una richiesta POST a https://buy.itunes.apple.com/verifyReceipt. Nel corpo JSON della richiesta, passa il segreto condiviso nel campo password e la ricevuta nel campo receipt-data. Esiste anche il parametro facoltativo exclude-old-transactions. Se ha il valore true per ogni abbonamento con rinnovamento automatico, si riceverà solo l’ultima transazione, invece dell’intera cronologia dei rinnovi.
Ecco il payload della richiesta di convalida dell’acquisto:
{
"password": "f4d35830e3...52aae",
"receipt-data": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"exclude-old-transactions": false
}
Se stai lavorando in un ambiente Sandbox (cioè stai testando gli acquisti), invia le richieste di convalida a https://sandbox.itunes.apple.com/verifyReceipt. Il segreto condiviso, il payload e i formati di risposta rimangono invariati.
È importante notare che non sarà possibile convalidare una ricevuta creata in ambiente Sandbox su un server di produzione e viceversa. Per questo motivo, nei sistemi reali, la prassi migliore è quella di indirizzare la prima richiesta al server di produzione e reindirizzarla al server Sandbox nel caso in cui la chiave di stato restituisca il codice di errore 21007. Durante la revisione dell’app, questo comportamento è indispensabile, poiché consente ai dipendenti Apple di testare gli acquisti e agli utenti reali dell’app di effettuarli.
Tra gli altri errori da tenere a mente, c’è il codice d’errore 21004, che significa che stiamo usando il segreto sbagliato. È importante monitorarlo, poiché ha un impatto sia sull’esperienza dell’utente sia sull’accuratezza delle analisi. Nel peggiore dei casi, se all’utente non viene mai concesso l’accesso alle funzioni premium dopo averle pagate, l’app può essere rimossa dall’App Store.
Se la convalida è andata a buon fine (status=0), la risposta conterrà i dettagli delle transazioni dell’utente.
Ecco la risposta alla richiesta di convalida del pagamento:
{
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 123,
"app_item_id": 123,
"bundle_id": "com.adapty.sample_app",
"application_version": "1",
"download_id": 123,
"version_external_identifier": 123,
"receipt_creation_date": "2021-04-28 19:42:01 Etc/GMT",
"receipt_creation_date_ms": "1619638921000",
"receipt_creation_date_pst": "2021-04-28 12:42:01 America/Los_Angeles",
"request_date": "2021-08-09 18:26:02 Etc/GMT",
"request_date_ms": "1628533562696",
"request_date_pst": "2021-08-09 11:26:02 America/Los_Angeles",
"original_purchase_date": "2017-04-09 21:18:41 Etc/GMT",
"original_purchase_date_ms": "1491772721000",
"original_purchase_date_pst": "2017-04-09 14:18:41 America/Los_Angeles",
"original_application_version": "1",
"in_app": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED"
}
]
},
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
],
"status": 0
}
La risposta è piuttosto macchinosa ed è stata semplificata nella nuova versione dell’API del server dell’App Store, ma l’attuale implementazione (implementation) non è così difficile da ottenere.
Per sapere se l’utente abbia accesso alle funzioni premium dell’app, è necessario un modo per determinare lo stato del suo abbonamento. Nella versione attuale dell’API non esiste una richiesta dedicata per recuperare lo stato dell’abbonamento, quindi è necessario lavorare comunque con la cronologia delle transazioni.
L’array latest_receipt_info per impostazione predefinita, contiene tutte le transazioni di acquisto in-app di un determinato utente, ad eccezione dei prodotti consumabili, che vengono completati sul lato dell’app. In questo modo è possibile recuperare l’intera cronologia degli acquisti dell’utente. Questo è molto utile sia per l’analisi che per determinare lo stato attuale dell’abbonamento.
Sembra che le transazioni arrivino sempre già ordinate dalle più recenti. Per essere sicuri, tuttavia, si consiglia di implementare l’ordinamento per data di transazione.
Il payload della transazione:
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
Per verificare lo stato attuale dell’abbonamento, è sufficiente recuperare l’ultima transazione della catena e guardare la proprietà expires_date. L’eccezione è rappresentata dal periodo di tolleranza (grace period), di cui parleremo più avanti.
Ai fini analitici, si consiglia di salvare le seguenti proprietà:
Alla WWDC 2021, Apple ha annunciato che sta pianificando di aggiungere un campo appAccountToken per le info sulle transazioni. Il campo conterrà l’identificativo utente nel tuo sistema. Questo identificativo deve essere in formato UUID e viene definito lato app quando viene inizializzato l’acquisto. Se definito, verrà restituito in tutte le transazioni di questa catena (rinnovi di abbonamenti (subscriptions renewals), problemi di fatturazione (billing issues), ecc., il che significa che sarà facile per te capire quale utente abbia effettuato l’acquisto.
Sarà bene che tu tenga traccia anche del parametro subscription_group_identifier. Se l’utente aveva in precedenza transazioni con prove attive o offerte introduttive, non dovrebbe avere accesso a queste per lo stesso gruppo di abbonamenti. Questo dovrebbe essere tracciato lato server.
L’array pending_renewal_info memorizza i dati di rinnovo dell’abbonamento. Permette di capire cosa succederà all’abbonamento nel prossimo periodo di fatturazione. Ad esempio, se si scopre che l’utente ha rinunciato al rinnovo automatico, si può suggerire di passare a un piano diverso o di presentare un’offerta promozionale. Questi eventi (events) possono essere facilmente monitorati con le notifiche del server (server notifications), di cui parleremo tra poco.
Dati di rinnovo dell’abbonamento:
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
Se l’utente non ha prodotti auto-rinnovabili (auto-renewable products), non verranno restituite le chiavi latest_receipt_info e pending_renewal_info. In questo caso, le transazioni si trovano in receipt → in_app. Il formato della transazione è simile a quello delle transazioni con rinnovo automatico, ma non presenta campi per la scadenza, i rinnovi, le offerte e altre proprietà esclusive delle transazioni con rinnovo automatico.
È opportuno notare che receipt → in_app arriverà anche per le transazioni auto-rinnovabili, ma la pratica migliore è quella di usare latest_receipt_info, poiché i dati relativi all’abbonamento in esso contenuti saranno i più aggiornati.
Fino a qualche tempo fa si doveva escogitare un sistema complesso, per tenere traccia dei cambiamenti dello stato degli abbonamenti. Ad esempio, per capire se l’abbonamento fosse stato rinnovato o meno, era necessario inviare una richiesta di verifica dello stato dell’abbonamento ai server Apple ogni ora a partire da 24 ore prima della sua scadenza. Apple ha aggiunto sempre più notifiche server, nel corso del tempo, che ora coprono praticamente tutti gli eventi importanti relativi agli abbonamenti. Questo sarà molto utile: quando ci saranno modifiche da parte di Apple, riceverai una notifica sul tuo server. In altre parole, riceverai una notifica sui nuovi acquisti, sui rinnovi degli abbonamenti, sui problemi di fatturazione, ecc. Ciò consente di raccogliere analisi molto più accurate e di gestire lo stato dell’abbonamento in modo molto più semplice.
Le notifiche del server possono essere abilitate in App Store Connect. Apri la pagina dell’applicazione e vai su General -> App information. Quindi, inserisci il link nel campo URL per le notifiche del server App Store e salva le modifiche.
Notifica del server:
{
"notification_type": "DID_RENEW",
"password": "f4d35830e3...52aae",
"environment": "PROD",
"auto_renew_product_id": "basic_subscription_1_month",
"auto_renew_status": "true",
"unified_receipt": {
"status": 0,
"environment": "Production",
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_status": "1",
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853"
}
]
},
"bid": "com.adapty.sample_app",
"bvrs": "0"
}
Il formato della notifica del server è simile alla risposta di convalida del pagamento. I dettagli della transazione sono memorizzati in unified_receipt → latest_receipt_info. La chiave password memorizza il segreto condiviso della tua app, consentendo di verificare l’autenticità della richiesta. La chiave notification_type contiene il tipo di evento. A mio parere, i più utili sono:
La convalida del server può potenziare le analisi dei dati raccolti dalla tua app. Inoltre, rende più difficile ai truffatori l’accesso ai tuoi contenuti premium e ti permette di rendere i tuoi abbonamenti multipiattaforma. Allo stesso tempo, l’implementazione della convalida del server può richiedere molto tempo, soprattutto se si ha bisogno di dati molto precisi. Ciò richiederebbe di prendere in considerazione molti casi secondari: upgrade o crossgrade dell’abbonamento, periodi di prova, offerte promozionali/introduttive, periodi di tolleranza, rimborsi, abbonamenti familiari, ecc. È inoltre necessario conoscere e tenere conto di varie sfumature, ad esempio, la politica di Apple di abbassare la commissione dal 30% al 15% per gli abbonamenti che vengono rinnovati regolarmente per oltre un anno.
Further reading
Tendenze-insight
Settembre 6, 2022