Tutti gli articoli
Deep dive8 min di lettura

Sia Apple sia Google possono consegnare lo stesso rimborso al tuo server più di una volta, e le notifiche di rimborso duplicate ti costano se agisci su ognuna

Apple riprova una notifica di rimborso fino a cinque volte e Google Play viaggia su Pub/Sub con consegna almeno una volta, quindi lo stesso rimborso può raggiungere il tuo server più di una volta. Ecco come gestire le notifiche di rimborso duplicate senza scalare un saldo né consumare quota API due volte.

Molte buste di carta identiche impilate su una scrivania scura con una spostata di lato, a rappresentare le notifiche di rimborso duplicate che arrivano al tuo server

Punti chiave

  • Apple riprova una App Store Server Notification V2 cinque volte, a 1, 12, 24, 48 e 72 ore dopo l'ultimo tentativo, ogni volta che il tuo server non risponde con uno stato HTTP tra 200 e 206. Contando il primo tentativo, un rimborso può arrivare fino a sei volte.
  • Ogni vero nuovo tentativo di Apple porta lo stesso notificationUUID, quindi quel campo, e non l'id della transazione, è la tua chiave di deduplicazione.
  • Le Real-time Developer Notifications di Google Play viaggiano su Cloud Pub/Sub, che garantisce la consegna almeno una volta e nessun ordine, quindi lo stesso messaggio può arrivare due volte o fuori ordine. Google ti dice di verificare l'unicità del messageId prima di elaborare qualsiasi cosa.
  • Le notifiche periodiche CONSUMPTION_REQUEST di Apple non sono nuovi tentativi. Apple continua a inviarne di nuove per tutta la finestra di rimborso aperta, ciascuna con un notificationUUID diverso, quindi deduplicare su notificationUUID le conserva correttamente tutte.
  • Lo stesso transactionId di Apple può portare più di una decisione, per esempio un REFUND_DECLINED seguito più tardi da un REFUND, quindi deduplicare solo sull'id della transazione scarta un evento distinto di cui avevi bisogno.
  • Rifiutare un duplicato restituendo un 4xx o un 5xx fa solo sì che lo store lo riprovi. Deduplica dentro il tuo database e restituisci sempre uno stato di successo.
  • Un gestore di rimborsi che non è idempotente agisce due volte alla seconda consegna. Scala un saldo due volte, inverte un pagamento due volte, o consuma quota fatturabile della Play Developer API e della App Store Server API ricontrollando un rimborso che ha già chiuso.

Il tuo server riceverà lo stesso evento di rimborso più di una volta, ed entrambi gli store lo hanno progettato così di proposito. Apple riprova una App Store Server Notification fino a cinque volte quando il tuo endpoint non risponde in modo pulito. Google Play consegna le sue Real-time Developer Notifications tramite Cloud Pub/Sub, che promette la consegna almeno una volta e nulla sull'ordine. Quindi la domanda non è mai se arriva un duplicato. È cosa fa il tuo codice la seconda volta che vede lo stesso rimborso. Sbaglia in questo e scali un saldo due volte, inverti un pagamento due volte, o consumi quota API fatturabile ricontrollando un rimborso che hai già chiuso. Ecco come le notifiche di rimborso duplicate ti raggiungono davvero, quali ripetizioni sono duplicati reali e quali solo lo sembrano, e come gestirle perché la seconda consegna sia gratuita.

Una notifica di rimborso viene consegnata almeno una volta, il che non è lo stesso che esattamente una volta

Entrambi gli store trattano una notifica consegnata come una promessa che continuano a cercare di mantenere, non come un colpo singolo che sparano e dimenticano. Questo è positivo per l'affidabilità, perché una notifica che perdi durante un deploy ti raggiunge comunque più tardi. È una trappola per la correttezza, perché il meccanismo che garantisce che alla fine ricevi l'evento garantisce anche che a volte lo ricevi due volte. Il tuo gestore deve essere idempotente, cioè la seconda e la terza consegna di uno stesso rimborso non cambiano nulla che la prima non abbia già cambiato.

Apple riprova cinque volte nell'arco di tre giorni

Quando Apple invia una App Store Server Notification V2, si aspetta che il tuo server risponda con uno stato HTTP nell'intervallo da 200 a 206. Qualsiasi altra cosa, un 4xx o un 5xx, dice ad Apple che la consegna è fallita, e Apple riprova. Il calendario è fisso: cinque nuovi tentativi, a 1, 12, 24, 48 e 72 ore dopo il tentativo precedente. Contando il primo tentativo, un evento di rimborso può arrivare fino a sei volte, distribuito nell'arco di circa una settimana. Ognuno di quei nuovi tentativi porta lo stesso notificationUUID. Quel campo è la tua chiave di deduplicazione. Se hai già registrato un notificationUUID, la consegna che hai in mano è una ripetizione, e la risposta corretta è non memorizzare nulla di nuovo e restituire comunque 200.

Google Play viaggia su Pub/Sub, che promette almeno una volta e non dice nulla sull'ordine

Le Real-time Developer Notifications di Google Play vengono pubblicate su un topic di Cloud Pub/Sub. La garanzia di consegna di Pub/Sub è almeno una volta, e non offre alcuna garanzia di ordine. Ciò significa che lo stesso messaggio può essere consegnato al tuo endpoint più di una volta, e due messaggi per lo stesso acquisto possono arrivare fuori ordine. La guida di Google è esplicita: spacchetta il campo base64 data, leggi il messageId, e verifica di non averlo già visto prima di elaborare qualsiasi cosa. Un messageId duplicato è una ripetizione che salti. Due notifiche diverse su uno stesso acquisto devono comunque atterrare sullo stesso record, quindi indicizza il tuo stato memorizzato anche sul purchaseToken, e lascia che un evento successivo aggiorni la riga che uno precedente ha creato.

PiattaformaModello di consegnaDeduplicare suSegnale di successoSe non confermi la ricezione
App Store Server Notifications V2Fino a 6 tentativi: il primo, più 5 nuovi tentativi a 1, 12, 24, 48, 72 orenotificationUUIDHTTP da 200 a 206Apple riprova secondo il calendario fisso, poi si ferma
Google Play RTDN su Pub/SubAlmeno una volta, nessuna garanzia di ordinePub/Sub messageId, indicizzato per entità su purchaseTokenHTTP 200 al push, o un ack esplicitoPub/Sub rinvia allo scadere del termine dell'ack

Le ripetizioni che non sono duplicati

Non ogni notifica che assomiglia a una che hai già visto è un nuovo tentativo. Due comportamenti di Apple inviano eventi genuinamente nuovi che condividono un acquisto ma che devono essere elaborati ciascuno, e comprimerli con una deduplicazione ingenua scarta informazioni di cui avevi bisogno.

Apple invia nuovi CONSUMPTION_REQUEST, non nuovi tentativi

Durante una richiesta di rimborso aperta su un consumabile, Apple non invia un solo CONSUMPTION_REQUEST e attende. Ne invia di nuovi periodicamente per tutta la finestra di rimborso aperta finché il rimborso non viene chiuso. Il personale di Apple ha confermato che non sono nuovi tentativi, e l'indizio è il campo su cui deduplichi: ogni nuovo CONSUMPTION_REQUEST porta un notificationUUID diverso. Quindi una deduplicazione indicizzata su notificationUUID fa automaticamente la cosa giusta. Comprime i veri nuovi tentativi e conserva ogni prompt distinto. Ciò che non devi fare è deduplicare sull'id della transazione e sul tipo di notifica, perché questo silenzierebbe ogni CONSUMPTION_REQUEST dopo il primo e ti costerebbe la finestra di prova di 12 ore su quelli che hai scartato.

Una transazione può portare più di una decisione

Un singolo transactionId può produrre più di un esito di rimborso nel corso della sua vita. Apple può inviare un REFUND_DECLINED e poi, più tardi, un REFUND per la stessa transazione, e gli sviluppatori riferiscono di ricevere tre o più notifiche relative al rimborso per uno stesso id di transazione. Ognuna è un evento distinto con il proprio notificationUUID. Se la tua chiave di deduplicazione è l'id della transazione, la seconda decisione sembra un duplicato della prima e non vieni mai a sapere che il rimborso è stato infine concesso. L'id della transazione raggruppa gli eventi. Non li identifica.

Una pinza robotica che solleva un pacco duplicato da un nastro trasportatore in uno scomparto laterale, un'immagine a rappresentare la deduplicazione di notifiche di rimborso ripetute

Quanto ti costa davvero un duplicato

Una notifica di rimborso non è una spia di stato. Scatena azioni reali: revochi l'accesso, scali un saldo di consumabile, inverti un pagamento a un creatore, chiami la App Store Server API o la Play Developer API per confermare lo stato. Esegui una di queste una seconda volta su un duplicato e il costo è reale.

Segui il denaro. Revocare l'accesso due volte è innocuo, perché l'accesso è già sparito. Scalare un saldo due volte non lo è: un utente che ha comprato un pacchetto di monete e lo ha fatto rimborsare può essere spinto a un saldo negativo che il tuo team di supporto deve poi sbrogliare a mano. Invertire un pagamento due volte recupera denaro che hai già restituito una volta, e ora devi a un creatore delle scuse e una correzione. E ogni duplicato che rielabori contro una API dello store spende quota che Google ti avverte esplicitamente di proteggere, quindi una raffica di riconsegna di Pub/Sub durante un'interruzione può spingerti nel rate limiting proprio nel giorno in cui meno te lo puoi permettere.

Le finestre di prova del rimborso alzano la posta sul lato Apple. Se una deduplicazione ingenua silenzia i CONSUMPTION_REQUEST ripetuti che Apple invia per tutta la finestra di rimborso aperta, puoi perdere quello a cui dovevi rispondere, e un CONSUMPTION_REQUEST a cui non rispondi entro 12 ore è un rimborso che Apple spesso concede per impostazione predefinita. Non è un doppio addebito. È una vendita persa più il calcolo, le chiamate API, l'archiviazione e i pagamenti che hai già speso per consegnare l'acquisto, nulla dei quali il rimborso restituisce.

Modalità di guastoCosa va stortoQuanto costa
Riscalare un saldo su un REFUND duplicatoIl saldo di consumabile dell'utente diventa negativoTempo di supporto manuale per riconciliare, e una cattiva esperienza per il cliente
Invertire un pagamento due volteRecuperi denaro che hai già restituito una voltaUna correzione al creatore e una pulizia contabile
Rielaborare contro una API dello storeLe chiamate duplicate consumano quota della Play Developer API o della App Store Server APIRate limiting durante l'interruzione che ha causato la riconsegna
Deduplicare troppo i CONSUMPTION_REQUESTScarti un prompt di rimborso distinto come falso duplicatoUna finestra di 12 ore persa, quindi Apple concede il rimborso per impostazione predefinita

Come gestire le notifiche di rimborso duplicate senza agire due volte

Lo schema è lo stesso su entrambi gli store, con una chiave diversa. Registra la consegna, verifica la chiave prima di agire, agisci una volta, e di' sempre allo store che l'hai ricevuta.

  • Deduplica sull'id di consegna dello store, non sulla transazione. Usa notificationUUID per Apple e il messageId di Pub/Sub per Google Play. Memorizzalo con un vincolo di unicità in modo che un duplicato concorrente perda la corsa invece di agire due volte.
  • Rendi l'azione a valle idempotente di per sé. Indicizzare sull'id di consegna ferma la rielaborazione, ma scrivi anche l'effetto in modo che revocare, scalare o invertire verifichi prima lo stato attuale e sia sicuro da eseguire due volte.
  • Persisti prima, poi conferma la ricezione. Scrivi l'evento nel tuo database prima di restituire 200 o confermare il messaggio di Pub/Sub. Se confermi prima e la scrittura fallisce, lo store considera il messaggio consegnato e non lo invia mai più, e ora lo hai perso per sempre.
  • Restituisci sempre uno stato di successo, anche per un duplicato. Un 200 a 206 per Apple, un 200 al push di Pub/Sub per Google. Rifiutare una ripetizione con un errore fa solo sì che lo store la riprovi.
  • Raggruppa sull'entità, identifica sull'evento. Indicizza il tuo stato d'acquisto memorizzato sul purchaseToken o sull'originalTransactionId in modo che le consegne fuori ordine aggiornino una sola riga, ma tratta ogni notificationUUID o messageId come il proprio evento, perché un acquisto ne produce legittimamente diversi.

Una breve lista di controllo prima di fidarti del tuo webhook di rimborso

  • Le consegne di Apple sono deduplicate su notificationUUID, e una ripetizione non scrive nulla di nuovo ma restituisce comunque 200.
  • Le consegne di Google Play sono deduplicate sul messageId di Pub/Sub, verificato prima di qualsiasi elaborazione.
  • Lo stato d'acquisto è indicizzato su purchaseToken o originalTransactionId, in modo che gli eventi fuori ordine atterrino su un solo record.
  • Ogni effetto collaterale del rimborso, revocare, scalare o invertire, è sicuro da eseguire più di una volta.
  • Il tuo gestore scrive l'evento prima di confermare la ricezione, mai dopo.
  • I CONSUMPTION_REQUEST ripetuti sono trattati come prompt distinti, non come duplicati, quindi nessuna finestra di rimborso aperta viene scartata.

Fai passare un duplicato attraverso il tuo webhook di proposito e osserva come non cambia nulla la seconda volta. Questo è tutto il test. Un gestore di rimborsi che è sicuro da colpire due volte è uno di cui puoi smettere di preoccuparti nel momento in cui uno store decide di colpirlo sei volte.

Domande frequenti

Perché il mio server riceve la stessa notifica di rimborso dell'App Store più di una volta?
Perché Apple riprova una App Store Server Notification V2 fino a cinque volte, a 1, 12, 24, 48 e 72 ore dopo l'ultimo tentativo, ogni volta che il tuo server non risponde con uno stato HTTP tra 200 e 206. Ogni nuovo tentativo porta lo stesso notificationUUID, così puoi riconoscerlo e saltarlo.
Quale campo dovrei usare per deduplicare le App Store Server Notifications?
Usa il notificationUUID. Un vero nuovo tentativo ripete sempre lo stesso notificationUUID, mentre ogni evento genuinamente nuovo, incluso ogni nuovo CONSUMPTION_REQUEST, ne riceve uno diverso, quindi deduplicare su notificationUUID salta le ripetizioni senza scartare eventi distinti.
Le notifiche CONSUMPTION_REQUEST ripetute sono duplicati che dovrei ignorare?
No. Apple invia nuove notifiche CONSUMPTION_REQUEST periodicamente per tutta la finestra di rimborso aperta, e il personale di Apple conferma che non sono nuovi tentativi. Ognuna ha il proprio notificationUUID, quindi elaborale tutte. Scartarle rischia di far perdere la finestra di 12 ore che Apple ti dà per rispondere.
Come deduplico le Real-time Developer Notifications di Google Play?
Leggi il messageId di Pub/Sub di ogni notifica e confrontalo con quelli che hai già elaborato prima di agire, perché Pub/Sub consegna almeno una volta e può inviare lo stesso messaggio più di una volta. Google lo raccomanda esplicitamente per evitare l'elaborazione duplicata e lo spreco di quota API.
Dovrei restituire un errore per rifiutare una notifica di rimborso duplicata?
No. Restituire un 4xx o un 5xx dice allo store che la consegna è fallita, quindi riprova comunque. Deduplica dentro il tuo database e restituisci sempre uno stato di successo, HTTP da 200 a 206 per Apple o una conferma 200 per il push di Google Play.

Fonti e approfondimenti

RefundHalt

Il pilota automatico dei rimborsi per App Store e Google Play

Continua a leggere

La prossima richiesta di rimborso è già in arrivo.

Configura RefundHalt nel tempo che impiegheresti a leggere un'altra e-mail dell'assistenza su un rimborso che non sei riuscito a contestare.