Baik Apple maupun Google dapat mengirimkan refund yang sama ke server Anda lebih dari sekali, dan notifikasi refund duplikat merugikan Anda jika Anda bertindak atas setiap notifikasi
Apple mengulang notifikasi refund hingga lima kali dan Google Play berjalan di atas Pub/Sub yang menjamin pengiriman minimal sekali, sehingga refund yang sama bisa mencapai server Anda lebih dari sekali. Berikut cara menangani notifikasi refund duplikat tanpa mengurangi saldo atau menghabiskan kuota API dua kali.

Poin utama
- Apple mengulang App Store Server Notification V2 lima kali, pada 1, 12, 24, 48, dan 72 jam setelah percobaan terakhir, setiap kali server Anda tidak menjawab dengan status HTTP antara 200 dan 206. Termasuk percobaan pertama, satu refund bisa tiba hingga enam kali.
- Setiap percobaan ulang Apple yang sejati membawa notificationUUID yang sama, sehingga field itu, bukan id transaksi, adalah kunci deduplikasi Anda.
- Real-time Developer Notifications dari Google Play berjalan di atas Cloud Pub/Sub, yang menjamin pengiriman minimal sekali dan tanpa urutan, sehingga pesan yang sama bisa tiba dua kali atau tidak berurutan. Google memberitahu Anda untuk memeriksa keunikan messageId sebelum memproses apa pun.
- Notifikasi CONSUMPTION_REQUEST berkala dari Apple bukan percobaan ulang. Apple terus mengirim yang baru sepanjang jendela refund yang terbuka, masing-masing dengan notificationUUID yang berbeda, sehingga deduplikasi berdasarkan notificationUUID dengan benar mempertahankan setiap notifikasi tersebut.
- transactionId Apple yang sama bisa membawa lebih dari satu keputusan, misalnya REFUND_DECLINED yang kemudian diikuti REFUND, sehingga deduplikasi hanya berdasarkan id transaksi membuang sebuah peristiwa berbeda yang Anda butuhkan.
- Menolak duplikat dengan mengembalikan 4xx atau 5xx hanya membuat toko mengulanginya. Lakukan deduplikasi di dalam database Anda sendiri dan selalu kembalikan status berhasil.
- Handler refund yang tidak idempoten bertindak ganda pada pengiriman kedua. Ia mengurangi saldo dua kali, membalikkan pembayaran dua kali, atau menghabiskan kuota Play Developer dan App Store Server API yang berbayar dengan memeriksa ulang refund yang sudah ia tutup.
Server Anda akan menerima peristiwa refund yang sama lebih dari sekali, dan kedua toko merancangnya seperti itu dengan sengaja. Apple mengulang App Store Server Notification hingga lima kali ketika endpoint Anda tidak menjawab dengan bersih. Google Play mengirimkan Real-time Developer Notifications-nya melalui Cloud Pub/Sub, yang menjanjikan pengiriman minimal sekali dan tidak menjanjikan apa pun tentang urutan. Jadi pertanyaannya tidak pernah apakah duplikat akan tiba. Pertanyaannya adalah apa yang dilakukan kode Anda saat kedua kalinya melihat refund yang sama. Salah menanganinya, dan Anda mengurangi saldo dua kali, membalikkan pembayaran dua kali, atau menghabiskan kuota API berbayar dengan memeriksa ulang refund yang sudah Anda tutup. Berikut bagaimana notifikasi refund duplikat sebenarnya mencapai Anda, pengulangan mana yang merupakan duplikat sejati dan mana yang hanya tampak seperti itu, dan cara menanganinya sehingga pengiriman kedua gratis.
Notifikasi refund dikirimkan minimal sekali, yang tidak sama dengan tepat sekali
Kedua toko memperlakukan notifikasi yang dikirim sebagai janji yang terus mereka coba penuhi, bukan tembakan tunggal yang mereka lepaskan lalu lupakan. Itu bagus untuk keandalan, karena notifikasi yang Anda lewatkan saat deploy tetap mencapai Anda kemudian. Itu jebakan untuk kebenaran, karena mekanisme yang menjamin Anda akhirnya menerima peristiwa juga menjamin Anda kadang menerimanya dua kali. Handler Anda harus idempoten, artinya pengiriman kedua dan ketiga dari satu refund tidak mengubah apa pun yang belum diubah oleh yang pertama.
Apple mengulang lima kali selama tiga hari
Ketika Apple mengirim App Store Server Notification V2, ia mengharapkan server Anda menjawab dengan status HTTP dalam rentang 200 hingga 206. Apa pun selain itu, 4xx atau 5xx, memberitahu Apple bahwa pengiriman gagal, dan Apple mengulang. Jadwalnya tetap: lima percobaan ulang, pada 1, 12, 24, 48, dan 72 jam setelah percobaan sebelumnya. Termasuk percobaan pertama, satu peristiwa refund bisa tiba hingga enam kali, tersebar selama sekitar seminggu. Setiap percobaan ulang itu membawa notificationUUID yang sama. Field itu adalah kunci deduplikasi Anda. Jika Anda sudah mencatat sebuah notificationUUID, pengiriman yang sedang Anda pegang adalah pengulangan, dan respons yang benar adalah tidak menyimpan apa pun yang baru dan tetap mengembalikan 200.
Google Play berjalan di atas Pub/Sub, yang menjanjikan minimal sekali dan tidak mengatakan apa pun tentang urutan
Real-time Developer Notifications dari Google Play dipublikasikan ke topik Cloud Pub/Sub. Jaminan pengiriman Pub/Sub adalah minimal sekali, dan ia tidak memberikan jaminan urutan sama sekali. Itu berarti pesan yang sama bisa dikirim ke endpoint Anda lebih dari sekali, dan dua pesan untuk pembelian yang sama bisa tiba tidak berurutan. Panduan Google sendiri jelas: buka field data base64, baca messageId, dan periksa bahwa Anda belum pernah melihatnya sebelum memproses apa pun. messageId duplikat adalah pengulangan yang Anda lewati. Dua notifikasi berbeda tentang satu pembelian tetap harus mendarat pada catatan yang sama, jadi kunci state tersimpan Anda juga pada purchaseToken, dan biarkan peristiwa yang lebih baru memperbarui baris yang dibuat peristiwa yang lebih awal.
| Platform | Model pengiriman | Deduplikasi berdasarkan | Sinyal berhasil | Jika Anda tidak mengakui |
|---|---|---|---|---|
| App Store Server Notifications V2 | Hingga 6 percobaan: yang pertama, plus 5 percobaan ulang pada 1, 12, 24, 48, 72 jam | notificationUUID | HTTP 200 hingga 206 | Apple mengulang pada jadwal tetap, lalu berhenti |
| Google Play RTDN melalui Pub/Sub | Minimal sekali, tanpa jaminan urutan | Pub/Sub messageId, dikunci entitas pada purchaseToken | HTTP 200 ke push, atau ack eksplisit | Pub/Sub mengirim ulang setelah tenggat ack habis |
Pengulangan yang bukan duplikat
Tidak setiap notifikasi yang tampak seperti yang pernah Anda lihat adalah percobaan ulang. Dua perilaku Apple mengirim peristiwa yang benar-benar baru yang berbagi satu pembelian tetapi masing-masing harus diproses, dan menggabungkannya dengan deduplikasi naif membuang informasi yang Anda butuhkan.
Apple mengirim CONSUMPTION_REQUEST baru, bukan percobaan ulang
Selama permintaan refund yang terbuka pada consumable, Apple tidak mengirim satu CONSUMPTION_REQUEST lalu menunggu. Ia mengirim yang baru secara berkala sepanjang seluruh jendela refund yang terbuka hingga refund ditutup. Staf Apple telah mengonfirmasi bahwa ini bukan percobaan ulang, dan petunjuknya ada di field yang Anda gunakan untuk deduplikasi: setiap CONSUMPTION_REQUEST baru membawa notificationUUID yang berbeda. Jadi deduplikasi yang dikunci pada notificationUUID melakukan hal yang benar secara otomatis. Ia menggabungkan percobaan ulang sejati dan mempertahankan setiap prompt yang berbeda. Yang tidak boleh Anda lakukan adalah deduplikasi berdasarkan id transaksi dan tipe notifikasi, karena itu akan membungkam setiap CONSUMPTION_REQUEST setelah yang pertama dan merugikan Anda jendela bukti 12 jam pada yang Anda buang.
Satu transaksi bisa membawa lebih dari satu keputusan
Satu transactionId bisa menghasilkan lebih dari satu hasil refund selama masa hidupnya. Apple bisa mengirim sebuah REFUND_DECLINED lalu, kemudian, sebuah REFUND untuk transaksi yang sama, dan para pengembang melaporkan menerima tiga atau lebih notifikasi terkait refund untuk satu id transaksi. Masing-masing adalah peristiwa berbeda dengan notificationUUID-nya sendiri. Jika kunci deduplikasi Anda adalah id transaksi, keputusan kedua tampak seperti duplikat dari yang pertama dan Anda tidak pernah tahu bahwa refund akhirnya diberikan. Id transaksi mengelompokkan peristiwa. Ia tidak mengidentifikasinya.

Apa yang sebenarnya merugikan Anda dari sebuah duplikat
Notifikasi refund bukan lampu status. Ia memicu tindakan nyata: Anda mencabut akses, Anda mengurangi saldo consumable, Anda membalikkan pembayaran kreator, Anda memanggil App Store Server API atau Play Developer API untuk mengonfirmasi state. Jalankan salah satunya untuk kedua kali pada sebuah duplikat dan biayanya nyata.
Telusuri uangnya. Mencabut akses dua kali tidak berbahaya, karena akses sudah hilang. Mengurangi saldo dua kali bukan tidak berbahaya: pengguna yang membeli sepaket koin lalu me-refund-nya bisa terdorong ke saldo negatif yang kemudian harus diurai tim dukungan Anda secara manual. Membalikkan pembayaran dua kali menarik kembali uang yang sudah Anda kembalikan sekali, dan sekarang Anda berutang permintaan maaf dan koreksi kepada seorang kreator. Dan setiap duplikat yang Anda proses ulang terhadap API toko menghabiskan kuota yang secara eksplisit diperingatkan Google untuk Anda lindungi, sehingga ledakan pengiriman ulang Pub/Sub selama gangguan bisa mendorong Anda ke pembatasan laju pada hari ketika Anda paling tidak bisa menanggungnya.
Jendela bukti refund menaikkan taruhan di sisi Apple. Jika deduplikasi naif membungkam CONSUMPTION_REQUEST berulang yang dikirim Apple sepanjang jendela refund yang terbuka, Anda bisa melewatkan yang perlu Anda jawab, dan CONSUMPTION_REQUEST yang tidak Anda jawab dalam 12 jam adalah refund yang sering diberikan Apple secara default. Itu bukan pengenaan biaya ganda. Itu adalah penjualan yang hilang ditambah komputasi, panggilan API, penyimpanan, dan pembayaran yang sudah Anda keluarkan untuk mengirimkan pembelian, tak satu pun dari itu dikembalikan oleh refund.
| Mode kegagalan | Apa yang salah | Apa yang dirugikan |
|---|---|---|
| Mengurangi ulang saldo pada REFUND duplikat | Saldo consumable pengguna menjadi negatif | Waktu dukungan manual untuk merekonsiliasi, dan pengalaman pelanggan yang buruk |
| Membalikkan pembayaran dua kali | Anda menarik kembali uang yang sudah Anda kembalikan sekali | Koreksi kreator dan pembersihan akuntansi |
| Memproses ulang terhadap API toko | Panggilan duplikat menghabiskan kuota Play Developer atau App Store Server API | Pembatasan laju selama gangguan yang menyebabkan pengiriman ulang |
| Deduplikasi CONSUMPTION_REQUEST berlebihan | Anda membuang prompt refund berbeda sebagai duplikat palsu | Jendela 12 jam yang terlewat, sehingga Apple memberikan refund secara default |
Cara menangani notifikasi refund duplikat tanpa bertindak ganda
Polanya sama di kedua toko, dengan kunci yang berbeda. Catat pengiriman, periksa kunci sebelum Anda bertindak, bertindak sekali, dan selalu beri tahu toko bahwa Anda menerimanya.
- Lakukan deduplikasi berdasarkan id pengiriman toko, bukan transaksi. Gunakan
notificationUUIDuntuk Apple dan Pub/SubmessageIduntuk Google Play. Simpan dengan constraint unik agar duplikat konkuren kalah dalam perlombaan alih-alih bertindak ganda. - Buat tindakan hilir idempoten menurut ketentuannya sendiri. Mengunci pada id pengiriman menghentikan pemrosesan ulang, tetapi tulis juga efeknya sehingga mencabut, mengurangi, atau membalikkan memeriksa state saat ini lebih dulu dan aman dijalankan dua kali.
- Simpan dulu, baru akui. Tulis peristiwa ke database Anda sebelum Anda mengembalikan 200 atau ack pesan Pub/Sub. Jika Anda ack lebih dulu dan penulisannya gagal, toko menganggap pesan terkirim dan tidak pernah mengirimnya lagi, dan sekarang Anda kehilangannya selamanya.
- Selalu kembalikan status berhasil, bahkan untuk duplikat. 200 hingga 206 untuk Apple, 200 ke push Pub/Sub untuk Google. Menolak pengulangan dengan error hanya membuat toko mengulanginya.
- Kelompokkan pada entitas, identifikasi pada peristiwa. Kunci state pembelian tersimpan Anda pada
purchaseTokenatauoriginalTransactionIdagar pengiriman yang tidak berurutan memperbarui satu baris, tetapi perlakukan setiapnotificationUUIDataumessageIdsebagai peristiwanya sendiri, karena satu pembelian secara sah menghasilkan beberapa.
Daftar periksa singkat sebelum Anda mempercayai webhook refund Anda
- Pengiriman Apple dideduplikasi berdasarkan
notificationUUID, dan pengulangan tidak menulis apa pun yang baru tetapi tetap mengembalikan 200. - Pengiriman Google Play dideduplikasi berdasarkan Pub/Sub
messageId, diperiksa sebelum pemrosesan apa pun. - State pembelian dikunci pada
purchaseTokenatauoriginalTransactionId, sehingga peristiwa yang tidak berurutan mendarat pada satu catatan. - Setiap efek samping refund, mencabut, mengurangi, atau membalikkan, aman dijalankan lebih dari sekali.
- Handler Anda menulis peristiwa sebelum ia mengakui, tidak pernah sesudahnya.
- CONSUMPTION_REQUEST yang berulang diperlakukan sebagai prompt berbeda, bukan duplikat, sehingga tidak ada jendela refund terbuka yang dibuang.
Jalankan sebuah duplikat melalui webhook Anda sendiri dengan sengaja dan lihat ia tidak mengubah apa pun untuk kedua kali. Itulah keseluruhan tesnya. Handler refund yang aman untuk dipukul dua kali adalah handler yang bisa Anda berhenti khawatirkan saat sebuah toko memutuskan untuk memukulnya enam kali.
Pertanyaan yang sering diajukan
- Mengapa server saya menerima notifikasi refund App Store yang sama lebih dari sekali?
- Karena Apple mengulang App Store Server Notification V2 hingga lima kali, pada 1, 12, 24, 48, dan 72 jam setelah percobaan terakhir, setiap kali server Anda tidak merespons dengan status HTTP antara 200 dan 206. Setiap percobaan ulang membawa notificationUUID yang sama, sehingga Anda bisa mengenalinya dan melewatinya.
- Field apa yang harus saya gunakan untuk mendeduplikasi App Store Server Notifications?
- Gunakan notificationUUID. Percobaan ulang yang sejati selalu mengulang notificationUUID yang sama, sedangkan setiap peristiwa yang benar-benar baru, termasuk setiap CONSUMPTION_REQUEST baru, mendapat yang berbeda, sehingga deduplikasi berdasarkan notificationUUID melewati pengulangan tanpa membuang peristiwa berbeda.
- Apakah notifikasi CONSUMPTION_REQUEST yang berulang adalah duplikat yang harus saya abaikan?
- Tidak. Apple mengirim notifikasi CONSUMPTION_REQUEST baru secara berkala sepanjang jendela refund yang terbuka, dan staf Apple mengonfirmasi bahwa ini bukan percobaan ulang. Masing-masing memiliki notificationUUID-nya sendiri, jadi proses setiap satu. Membuangnya berisiko melewatkan jendela 12 jam yang diberikan Apple untuk Anda merespons.
- Bagaimana cara saya mendeduplikasi Google Play Real-time Developer Notifications?
- Baca Pub/Sub messageId dari setiap notifikasi dan periksa terhadap yang sudah Anda proses sebelum bertindak, karena Pub/Sub mengirim minimal sekali dan bisa mengirim pesan yang sama lebih dari sekali. Google merekomendasikan ini secara eksplisit untuk menghindari pemrosesan duplikat dan kuota API yang terbuang.
- Haruskah saya mengembalikan error untuk menolak notifikasi refund duplikat?
- Tidak. Mengembalikan 4xx atau 5xx memberitahu toko bahwa pengiriman gagal, sehingga ia tetap mengulanginya. Lakukan deduplikasi di dalam database Anda sendiri dan selalu kembalikan status berhasil, HTTP 200 hingga 206 untuk Apple atau pengakuan 200 untuk push Google Play.
Sumber dan bacaan lanjutan
- Apple Developer: App Store Server Notifications V2
- Apple Developer: Responding to App Store Server Notifications
- Apple Developer: notificationUUID
- Apple Developer Forums: App Store Server Notifications V2 retry schedule and CONSUMPTION_REQUEST clarification
- Android Developers: Real-time developer notifications reference
- Android Developers: Purchase lifecycle and RTDNs
- Google Cloud: Pub/Sub subscriber and at-least-once delivery
RefundHalt
Autopilot refund untuk App Store dan Google Play
Baca selanjutnya
Pengembalian dana Family Sharing membalikkan satu pembayaran tetapi bisa membuat lima orang lain tetap memakai aplikasi Anda, dan hanya server Anda yang dapat memutus akses mereka
Pengembalian dana Family Sharing membalikkan satu pembayaran tetapi bisa membuat hingga lima anggota keluarga tetap menikmati fitur berbayar Anda. Apple mengirim REVOKE dan mengharapkan server Anda mengakhiri akses tersebut. Berikut cara kerja pengembalian dana berbagi keluarga dan berapa biayanya.
Penanganan refund rusak dengan diam-diam, jadi uji refund pembelian dalam aplikasi di sandbox sebelum pelanggan asli melakukannya
Penanganan refund Anda hanya berjalan setelah pelanggan sudah pergi, jadi bug di dalamnya tetap tak terlihat sampai merugikan uang sungguhan. Kedua toko memungkinkan Anda memicu refund di lingkungan uji terlebih dahulu. Berikut cara menguji refund pembelian dalam aplikasi di App Store dan Google Play sebelum satu pun menjadi nyata.