Mencabut Akses Setelah Refund: Langkah yang Tidak Diambil Apple dan Google untuk Anda
Apple dan Google bisa saja memproses refund sementara pelanggan tetap memiliki pembelian tersebut. Berikut kapan tepatnya akses dicabut secara otomatis, kapan server Anda yang harus melakukannya, dan satu notifikasi yang hampir tidak pernah ditangani oleh kebanyakan integrasi.

Poin utama
- Di Google Play, me-refund pesanan tidak dengan sendirinya mencabut akses pengguna. Akses hanya dicabut jika opsi "Remove entitlement" dicentang di Play Console, atau jika panggilan API menyertakan revoke=true, yang bukan merupakan default.
- Refund yang diproses tanpa opsi revoke tidak akan pernah muncul di Voided Purchases API. Proses revocation yang hanya membaca feed tersebut akan diam-diam melewatkan setiap pesanan semacam itu.
- Apple dapat mencabut lisensi untuk non-consumable dan subscription secara otomatis, tetapi consumable dianggap sudah terkirim begitu ia digunakan, sehingga StoreKit tidak memiliki mekanisme untuk mencabutnya. Server Anda sendiri yang harus mengurangi saldonya.
- Apple juga dapat membatalkan refund yang sudah diberikannya sendiri. Notifikasi REFUND_REVERSED berarti sebuah refund yang sebelumnya disetujui dibatalkan, revocationDate kembali menjadi null, dan akses atau saldo apa pun yang telah Anda hapus harus dipulihkan.
- Field voidedReason milik Google membawa sembilan nilai, mulai dari remorse dan accidental_purchase hingga fraud, friendly_fraud, dan unacknowledged_purchase, sehingga feed yang sama yang memberi tahu Anda untuk mencabut akses juga memberi tahu Anda mana pencabutan yang merupakan penyalahgunaan dan mana yang merupakan bug pada integrasi Anda sendiri.
- Setiap hari sebuah pembelian yang di-refund tetap memiliki akses adalah hari di mana Anda membayar biaya compute, storage, dan panggilan API di baliknya, atas uang yang sudah Anda kembalikan.
Menyetujui refund dan mencabut akses adalah dua langkah yang terpisah, dan hanya salah satunya yang terjadi secara otomatis. Developer yang menganggap refund yang sudah diproses berarti pembelian sudah dicabut aksesnya keliru di kedua toko besar, dengan cara dan alasan yang berbeda. Mengetahui persis di mana otomatisasi berhenti adalah yang dibutuhkan untuk mencabut akses setelah refund, alih-alih baru menyadarinya berbulan-bulan kemudian bahwa hal itu tidak pernah terjadi.
Mengapa refund tidak secara default mencabut akses
Apple dan Google sama-sama memperlakukan uang dan akses sebagai dua operasi yang berbeda. Sisi uang adalah kebijakan toko, begitu refund disetujui, statusnya final dan buku besar diperbarui. Sisi akses bersifat opsional, atau menjadi tugas developer, atau keduanya, tergantung pada platform dan jenis pembelian. Tidak ada toko yang berasumsi Anda ingin akses langsung hilang begitu refund selesai diproses, karena banyak kasus yang sah (refund sebagian, kredit itikad baik, gestur dukungan pelanggan) yang memang tidak dimaksudkan untuk mengambil kembali produknya sama sekali.
Google Play: kotak centang yang tidak disadari siapa pun
Di Play Console, me-refund sebuah pesanan adalah formulir dua bagian, pilih jumlah refund, lalu tentukan secara terpisah apakah akan mencentang "Remove entitlement". Membiarkannya tidak tercentang adalah jalur default yang diambil sebagian besar agen support, karena kebanyakan permintaan refund adalah gestur itikad baik di mana pelanggan tetap memiliki apa yang mereka beli. Masalahnya, tidak ada apa pun di layar konfirmasi refund yang memberi tahu Anda bahwa ini juga cara pelanggan tetap memiliki subscription atau item in-app setelah refund yang seharusnya Anda maksudkan sebagai final.
Jalur API memiliki jebakan yang sama
Pilihan yang sama ada di dalam kode. Endpoint orders.refund menerima parameter query revoke yang bersifat opsional, ketika nilainya true, akses ke item tersebut langsung dihentikan, dan untuk subscription berulang, setiap pembayaran di masa depan juga ikut dibatalkan. Jika parameter ini dihilangkan, nilainya default ke false, refund tetap diproses, catatan pembelian diperbarui, dan pengguna tetap dapat menggunakan produk persis seperti sebelumnya.
| Tindakan | Akses dicabut? | Muncul di Voided Purchases API? |
|---|---|---|
| Refund Play Console, "Remove entitlement" tidak dicentang | Tidak | Tidak |
| Refund Play Console, "Remove entitlement" dicentang | Ya, segera | Ya |
| API orders.refund, parameter revoke dihilangkan (default false) | Tidak | Tidak |
| API orders.refund, revoke=true | Ya, segera | Ya |
Baris terakhir itulah yang menjebak banyak integrasi. Voided Purchases API dibuat agar Anda bisa membangun pipeline revocation tanpa harus memeriksa Play Console secara manual, tetapi API ini hanya mendaftar pesanan yang benar-benar dibatalkan dengan revocation. Refund yang diproses tanpa kotak centang itu, atau tanpa revoke=true, memang sengaja tidak terlihat oleh feed tersebut, bukan karena bug.

Membaca field voidedReason Ketika sebuah pembelian memang muncul sebagai voided, field voidedReason memberi tahu Anda alasannya, dalam skala sembilan nilai: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback, dan unacknowledged_purchase. Itu bukan sekadar pencatatan. Remorse dan accidental_purchase adalah refund biasa. Fraud, friendly_fraud, dan chargeback adalah nilai-nilai yang layak diarahkan ke antrean review fraud, bukan ke jalur revoke-and-forget biasa, karena akun yang sama secara statistik lebih mungkin mengulanginya.
Ada satu detail operasional yang penting jika Anda membangun sistem langsung di atas feed ini, purchases.voidedpurchases.list dibatasi hingga 6.000 query per hari dan 30 query dalam jendela 30 detik mana pun. Desain yang melakukan polling dalam loop ketat akan cepat mencapai batas itu. Pola yang dimaksudkan adalah pull terjadwal dengan pagination token, bukan polling terus-menerus.
Apple: App Store dapat mencabut lisensi, tetapi tidak untuk consumable
App Store Server Notifications V2 milik Apple mengirimkan notifikasi REFUND begitu sebuah refund selesai diproses, baik untuk consumable, non-consumable, subscription auto-renewable, maupun subscription non-renewing. Apa yang terjadi setelah notifikasi itu sepenuhnya bergantung pada jenis pembeliannya, karena mekanisme revocation milik StoreKit sendiri hanya mencakup dua dari empat jenis tersebut.
Mengapa consumable menjadi pengecualian
Non-consumable atau subscription memiliki entitlement yang tetap ada dan bisa dimatikan oleh sistem Apple. Consumable tidak demikian, begitu digunakan, ia dianggap sudah terkirim, dan tidak ada lagi yang tersisa di StoreKit untuk dicabut. Jika seorang pelanggan membeli 500 coins, menghabiskannya, lalu mendapatkan refund, coins tersebut sudah dianggap tuntas dari sudut pandang Apple. Satu-satunya buku besar yang masih mencatatnya adalah milik Anda. Notifikasi REFUND adalah satu-satunya sinyal bagi Anda untuk menemukan original transaction ID yang cocok dan mengurangi saldo tersebut dari catatan Anda sendiri, tidak ada apa pun di sisi Apple yang akan melakukannya untuk Anda.
| Notifikasi | Artinya | Yang harus dilakukan server Anda |
|---|---|---|
| REFUND | Apple me-refund pembelian consumable, non-consumable, auto-renewable, atau non-renewing | Cabut akses non-consumable dan subscription segera; kurangi saldo consumable sendiri |
| REFUND_DECLINED | Apple menolak permintaan refund pelanggan | Tidak ada tindakan. Akses tidak pernah berisiko |
| REFUND_REVERSED | Apple membatalkan refund yang sebelumnya sudah diberikan | Pulihkan akses atau saldo apa pun yang tadinya Anda hapus akibat notifikasi REFUND |
| CONSUMPTION_REQUEST | Apple meminta bukti Anda sebelum memutuskan sebuah refund | Tanggapi dalam 12 jam; ini muncul sebelum REFUND, bukan sesudahnya |
Notifikasi yang hampir tidak pernah ditangani siapa pun
REFUND_REVERSED adalah notifikasi yang paling sering diabaikan sepenuhnya oleh kebanyakan integrasi, karena ia menggambarkan Apple membatalkan keputusannya sendiri, sebuah kasus yang jarang dirancang oleh developer sejak awal. Ketika notifikasi ini tiba, revocationDate pada transaksi tersebut kembali menjadi null, sejauh yang Apple ketahui, pembelian itu kembali sah. Jika server Anda menghapus entitlement, membatalkan catatan subscription, atau menolkan saldo consumable saat REFUND awal masuk, REFUND_REVERSED adalah sinyal untuk mengembalikan semuanya.
Cara menutup celah ini, langkah demi langkah
- Di Google Play, jadikan revoke=true sebagai default untuk setiap jalur refund, baik melalui Play Console maupun API, kecuali ada kasus support tertentu yang memang mengharuskan pelanggan tetap memiliki akses.
- Lakukan polling purchases.voidedpurchases.list secara terjadwal dengan pagination token, bukan dalam loop ketat, dan tetap di bawah 6.000 request per hari serta 30 per 30 detik.
- Arahkan nilai voidedReason berupa fraud, friendly_fraud, dan chargeback ke antrean review, bukan ke revoke-and-close otomatis.
- Di Apple, berlangganan App Store Server Notifications V2 dan cabut akses non-consumable serta subscription begitu notifikasi REFUND menyebutkannya.
- Simpan original transaction ID untuk setiap pembelian consumable agar notifikasi REFUND bisa menemukan saldo yang tepat untuk dikurangi tanpa pencarian manual.
- Tangani REFUND_REVERSED secara eksplisit dan catat setiap revocation berdasarkan transaction atau order ID-nya, agar pembatalan dapat memulihkan persis apa yang telah dihapus.
Apa sebenarnya biaya dari celah ini
Semua ini bukan soal keadilan bagi pelanggan, ini soal apa yang tetap berjalan setelah uangnya sudah hilang. Subscription yang sudah di-refund tetapi masih memanggil API Anda, menyimpan data, dan menghabiskan compute bukanlah masalah moral, itu adalah pos biaya, Anda membayar tagihan infrastruktur untuk pembelian yang pendapatannya sudah Anda kembalikan. Toko tidak melacak biaya itu untuk Anda, karena dari sisi mereka, refund sudah selesai begitu uangnya berpindah. Sisi akses sepenuhnya menjadi tagihan yang harus Anda kendalikan sendiri.
Inilah alasan yang sama mengapa RefundHalt memperlakukan revocation sebagai bagian dari pipeline refund, bukan sebagai renungan belakangan, sebuah event REFUND dan perubahan akses dicatat sebagai satu tindakan, dan sebuah event REFUND_REVERSED memulihkan state secara otomatis, alih-alih menunggu seseorang menyadari bahwa pelanggan masih terkunci dari sesuatu yang sudah dikembalikan oleh Apple.
Pertanyaan yang sering diajukan
- Apakah me-refund pembelian di Google Play secara otomatis mencabut akses?
- Tidak. Akses hanya dicabut jika "Remove entitlement" dicentang di Play Console atau panggilan API orders.refund menyertakan revoke=true. Keduanya bukan default, sehingga sebuah refund bisa diproses sementara pelanggan tetap menggunakan produknya.
- Apa yang terjadi ketika pembelian in-app consumable di-refund di App Store?
- Apple mengirimkan notifikasi REFUND, tetapi StoreKit tidak memiliki cara untuk mencabut consumable karena dianggap sudah terkirim begitu dipakai. Server milik developer harus menemukan transaksi aslinya dan mengurangi saldo secara manual.
- Apa itu notifikasi REFUND_REVERSED dan apakah memerlukan penanganan khusus?
- REFUND_REVERSED berarti Apple membatalkan refund yang sebelumnya sudah diberikan; revocationDate kembali menjadi null dan pembelian tersebut kembali sah. Akses atau saldo apa pun yang dihapus oleh REFUND awal harus dipulihkan, dan kebanyakan integrasi bahkan tidak pernah mendengarkan notifikasi ini sama sekali.
- Mengapa pesanan Google Play yang di-refund bisa tidak muncul di Voided Purchases API?
- API tersebut hanya mendaftar pesanan yang dibatalkan dengan revocation. Jika sebuah refund diproses tanpa opsi revoke, pesanan itu sama sekali tidak akan dikembalikan oleh endpoint tersebut, sehingga pipeline yang hanya mengandalkannya akan sepenuhnya melewatkan refund-refund itu.
- Seberapa sering saya boleh melakukan polling ke Voided Purchases API milik Google?
- Endpoint tersebut dibatasi hingga 6.000 query per hari dan 30 query dalam jendela 30 detik mana pun, sehingga dirancang untuk pull terjadwal dengan pagination token, bukan untuk polling terus-menerus.
Sumber dan bacaan lanjutan
- Apple: Handling refund notifications
- Apple: notificationType (App Store Server Notifications)
- Apple: revocationReason (App Store Server Notifications)
- Google: Voided Purchases API overview
- Google: purchases.voidedpurchases REST resource
- Google: orders.refund API reference (revoke parameter)
- Google Play Console Help: manage your app's orders and issue refunds
RefundHalt
Autopilot refund untuk App Store dan Google Play
Baca selanjutnya
The refunds you cannot contest: what Apple and Google decide without asking you
Only two refund flows ever ask for your evidence: Apple's consumption request and Google Play's chargeback review. Everything else, the 48-hour Play refund, support refunds, voided purchases, unacknowledged trials, is decided without you. Here is the full map, what you can prevent, and what you can only record.
Apple's CONSUMPTION_REQUEST, explained: the 12 hours that decide your refund
When a customer asks Apple for a refund, Apple asks you first. Here's exactly what the CONSUMPTION_REQUEST notification contains, what Apple expects back, how to answer one end to end, and why most developers silently forfeit.