Buat APK Gratis

Menghasilkan uang

Jual Produk Dalam Aplikasi (IAP)

Pembelian Google Play dari situs web atau aplikasi ZIP Anda sendiri

1πŸ“₯ Dapatkan panduan + aplikasi demo yang berfungsi#

Semua dalam panduan ini juga tersedia sebagai PDF yang bisa dibaca offline, plus aplikasi demo lengkap yang bisa Anda build dan jalankan hari ini.

Ketuk di bawah dan keduanya disimpan ke ponsel Anda di Downloads/AppMint/Guides:

  • AppMint-InApp-Purchases-Guide.pdf - panduan lengkap, termasuk langkah Play Console dan contoh React / Next.js
  • iap-demo-index.html - game kecil yang menjual tiga hal: hapus iklan (sekali bayar), paket level (sekali bayar), dan season pass bulanan

Untuk mencoba demo: zip iap-demo-index.html sendiri, unggah di stepper, dan ikuti panduan. Ini cara tercepat melihat pembelian sungguhan berjalan dari awal sampai akhir sebelum menyentuh aplikasi Anda sendiri.

Di aplikasi, tombol langkah ini menyimpannya ke ponsel Anda. Di sini berupa unduhan langsung.

AppMint-InApp-Purchases-Guide.pdf PDF Β· 257 KB iap-demo-index.html HTML Β· 10 KB

2Apa ini? (dengan bahasa sederhana)#

Aplikasi Anda bisa menjual sesuatu - peningkatan Pro, level tambahan, langganan - dan Google menerima uangnya lalu membayarkannya kepada Anda setiap bulan.

KITA AKAN MEMBUAT SATU CONTOH BERSAMA, sepanjang panduan ini:

A game that sells two things
  β€’ 'Levels 11-20'      β‚Ή99  paid once, kept forever
  β€’ 'Remove ads'        β‚Ή49  every month

Pada akhirnya Anda hanya menambahkan empat baris ke halaman Anda. Hanya itu pekerjaannya - tidak ada kode pembelian yang perlu ditulis, dan jika Anda belum pernah melakukannya, Anda tetap akan baik-baik saja.

Anda TIDAK perlu bisa pemrograman selain salin-tempel, dan TIDAK perlu server, database, atau akun perusahaan pembayaran. Google Play mengurus bagian yang sulit:

  • Menampilkan layar pembayaran (kartu / UPI / carrier billing)
  • Mengubah harga Anda ke mata uang setiap negara
  • Mengingat siapa membeli apa - bahkan setelah instal ulang
  • Mengirim uang ke bank Anda

AppMint mengambil 0% - penjualan langsung masuk ke akun Google Play ANDA.

Berfungsi sama untuk Website β†’ APK, ZIP β†’ APK, dan aplikasi buatan AI.

3Yang Anda butuhkan sebelum mulai#

Hanya empat hal:

  1. Situs web atau aplikasi HTML/ZIP Anda (yang Anda ubah menjadi APK).
  2. Sesuatu untuk dijual - tentukan apa yang didapat pembeli (mis. 'Pro version': tanpa batas, konten tambahan).
  3. Akun pengembang Google Play - biaya satu kali $25 di play.google.com/console. Anda membutuhkannya untuk menerbitkan APA PUN di Play Store, bukan hanya aplikasi berbayar.
  4. Sekitar 1 jam, total, tersebar di langkah-langkah di bawah.

Penting diketahui di awal: pembelian dalam aplikasi hanya berfungsi saat aplikasi Anda dipasang DARI Play Store. Itu aturan Google untuk semua orang, bukan batasan AppMint.

4Langkah 1 - Buat ID produk Anda#

ID produk hanyalah nama singkat yang Anda buat untuk barang yang Anda jual. Anda akan mengetik nama yang sama di dua tempat nanti (halaman Anda + Play Console), jadi buat sederhana dan catat.

Aturan: hanya huruf kecil, angka, garis bawah. Harus diawali huruf atau angka.

Salin salah satu dari ini:

  • pro_unlock - peningkatan 'Pro version' (default yang bagus - kode di bawah sudah memakainya)
  • remove_ads - peningkatan bebas iklan
  • level_pack_2 - paket konten tambahan
  • coins_100 - paket mata uang game
  • premium_monthly - langganan bulanan

Harga BUKAN bagian dari ID - Anda menetapkan harga di Langkah 5, dan bisa mengubahnya kapan saja tanpa menyentuh kode Anda.

5Langkah 2 - Tandai tombol Anda (tanpa kode yang perlu ditulis)#

Anda TIDAK menulis kode pembelian apa pun. Aplikasi Anda sudah membawa mesin pembelian - Anda hanya memberi label pada HTML Anda sendiri agar tahu apa yang dijual.

Salin blok di bawah ke halaman Anda dan ubah hanya:

(1) pro_unlock β†’ ID produk Anda, di data-iap-buy
(2) kata-kata di tombol dan di dalam bagian berbayar

Selesai. Tombol lalu menampilkan harga asli Google dalam mata uang pembeli sendiri, membuka layar pembayaran Google saat diketuk, menampilkan bagian berbayar setelah pembayaran (dan lagi di setiap peluncuran bagi yang sudah membeli), mencabutnya jika langganan berakhir atau pembayaran dikembalikan, dan menampilkan pesan jelas jika ada yang salah.

EMPAT LABEL:

  • data-iap-buy="id" - menjadikan ini tombol beli (wajib)
  • data-iap-label="…{price}…" - teks tombol; {price} menjadi harga asli dari Google
  • data-iap-owned="…" - teks tombol setelah membeli
  • data-iap-show - bagian yang muncul hanya setelah membeli (awali dengan 'hidden')

Tersedia juga: data-iap-hide (hilang setelah membeli), data-iap-container (pembungkus yang hanya muncul di dalam aplikasi), data-iap-text (taruh pada <span> yang berisi teks jika tombol Anda juga punya ikon).

MENJUAL BEBERAPA HAL BERBEDA? Tambahkan tombol untuk masing-masing. Mereka independen - satu aplikasi bisa menghapus iklan DAN menjual paket level DAN menjalankan langganan, dan membeli satu tidak memengaruhi yang lain. Arahkan satu bagian ke satu produk dengan data-iap-show="level_pack_2".

Hanya ID yang Anda ketik di 'Remove Ads (IAP) β†’ Product ID' yang mematikan iklan. Semua yang lain yang Anda jual membuka apa pun yang dilakukan halaman Anda dengannya, dan iklan tetap berjalan.

DUA PAKET UNTUK HAL YANG SAMA (bulanan vs seumur hidup)? Beri kedua tombol data-iap-group="adfree" yang sama - maka membeli salah satunya menyembunyikan yang lain, sehingga tidak ada yang membayar dua kali. Tanpa grup, tidak ada yang disembunyikan, yang memang dibutuhkan produk yang tidak berhubungan.

<!-- (1) CHANGE pro_unlock to your product ID -->
<button data-iap-buy="pro_unlock"
        data-iap-label="Unlock Pro β€” {price}"
        data-iap-owned="βœ“ Unlocked">
  Unlock Pro
</button>

<!-- (2) CHANGE the words. Appears only after buying. -->
<div data-iap-show hidden>
  πŸŽ‰ Pro content here β€” visible after buying.
</div>

<!-- Optional: disappears after buying -->
<div data-iap-hide>
  Upgrade to remove the ads.
</div>

<!-- DIFFERENT THINGS: independent β€” buying one never affects the others -->
<button data-iap-buy="remove_ads"   data-iap-label="Remove ads β€” {price}">Remove ads</button>
<button data-iap-buy="level_pack_2" data-iap-label="Levels 11-20 β€” {price}">Get levels</button>

<div data-iap-show="level_pack_2" hidden>Levels 11-20 unlocked!</div>

<!-- SAME THING, TWO PLANS: one group, so buying either hides the other -->
<button data-iap-buy="remove_ads_monthly"  data-iap-group="adfree"
        data-iap-label="Remove ads β€” {price}/month"
        data-iap-owned="βœ“ Ad-free (monthly)">Remove ads monthly</button>

<button data-iap-buy="remove_ads_lifetime" data-iap-group="adfree"
        data-iap-label="Remove ads forever β€” {price}"
        data-iap-owned="βœ“ Ads removed forever">Remove ads forever</button>

64 hal yang biasanya dijual orang#

Pilih yang cocok untuk Anda. Satu-satunya perbedaan adalah tempat Anda membuat produk di Play Console - bentuk kode halaman Anda selalu sama.

  1. HAPUS IKLAN, BAYAR SEKALI (selamanya)

Buat di: In-app products
Ketik juga ID-nya di: Remove Ads (IAP) β†’ Product ID

  1. HAPUS IKLAN, BULANAN

Buat di: Subscriptions
Ketik juga ID-nya di: Remove Ads (IAP) β†’ Product ID

  1. BUKA LEVEL / KONTEN, BAYAR SEKALI

Buat di: In-app products
JANGAN ketik di kotak Remove Ads - iklan Anda tetap berjalan.

  1. BUKA LEVEL / KONTEN, BULANAN

Buat di: Subscriptions
JANGAN ketik di kotak Remove Ads - iklan Anda tetap berjalan.

Anda bisa menjual KEEMPATNYA di aplikasi yang sama, dan produk sebanyak yang Anda mau. Membeli satu tidak memengaruhi yang lain.

<!-- 1. remove ads, paid once -->
<button data-iap-buy="remove_ads_lifetime"
        data-iap-label="Remove ads forever β€” {price}"
        data-iap-owned="βœ“ Ads removed">Remove ads forever</button>

<!-- 2. remove ads, monthly -->
<button data-iap-buy="remove_ads_monthly"
        data-iap-label="Remove ads β€” {price}/month"
        data-iap-owned="βœ“ Ad-free">Remove ads monthly</button>

<!-- 3. levels, paid once -->
<button data-iap-buy="level_pack_2"
        data-iap-label="Levels 11-20 β€” {price}"
        data-iap-owned="βœ“ Unlocked">Get levels 11-20</button>

<!-- 4. all levels, monthly -->
<button data-iap-buy="all_levels_monthly"
        data-iap-label="All levels β€” {price}/month"
        data-iap-owned="βœ“ Subscribed">Subscribe</button>

7Pembelian mana yang menghapus iklan?#

Ini satu hal yang sering salah, jadi layak 30 detik.

Iklan Anda BUKAN bagian dari halaman web Anda. Iklan itu adalah iklan Android asli yang berada di atasnya, jadi tidak ada di halaman Anda yang bisa mematikannya. Hanya SATU hal yang bisa:

wizard Step 3 β†’ Remove Ads (IAP) β†’ Product ID

ID apa pun yang Anda ketik di kotak itu akan menghapus iklan saat dibeli. ID lain yang Anda jual hanya membuka apa pun yang dilakukan halaman Anda dengannya, dan iklan tetap berjalan.

Menjual paket bebas iklan DAN paket level? Ketik hanya ID bebas iklan di kotak itu. Pada contoh kita:

Product ID box:  remove_ads_monthly
NOT in the box:  level_pack_2

Menawarkan dua paket bebas iklan (bulanan DAN seumur hidup)? Taruh keduanya di satu kotak itu, dipisahkan koma - memiliki salah satunya menghapus iklan:

remove_ads_monthly,remove_ads_lifetime

Tidak menjual penghapusan iklan sama sekali? Biarkan toggle itu mati dan gunakan saja 'πŸ’° Sell In-App Products'.

Selling levels only:
  Remove Ads (IAP)          OFF
  Sell In-App Products      ON

Selling ad removal only:
  Remove Ads (IAP)          ON   β†’ Product ID: remove_ads_monthly
  Sell In-App Products      ON

Selling both, with two ad-free plans:
  Remove Ads (IAP)          ON   β†’ Product ID: remove_ads_monthly,remove_ads_lifetime
  Sell In-App Products      ON
  (level_pack_2 stays out of the box)

8Contoh lengkap - halaman HTML biasa#

Ini seluruh aplikasi contoh kita: menjual 'Levels 11-20' sekali, dan 'Remove ads' bulanan. Salin, ubah dua ID dan kata-katanya, dan selesai.

Baca fungsi setiap label:

  • data-iap-buy β†’ apa yang dijual tombol ini
  • data-iap-label β†’ teksnya; {price} menjadi harga asli dari Google
  • data-iap-owned β†’ teks setelah mereka membayar
  • data-iap-show="level_pack_2" β†’ kotak ini muncul hanya setelah produk ITU dibeli
  • data-iap-hide β†’ kotak ini hilang setelah ada yang dibeli

Perhatikan tidak ada <script> dan tidak ada kode pembelian. Aplikasi Anda sudah memilikinya.

<h2>Get more levels</h2>

<button data-iap-buy="level_pack_2"
        data-iap-label="Levels 11-20 β€” {price}"
        data-iap-owned="βœ“ Levels 11-20 unlocked">
  Levels 11-20
</button>

<div data-iap-show="level_pack_2" hidden>
  πŸŽ‰ Levels 11 to 20 are yours. Have fun!
</div>


<h2>Tired of ads?</h2>

<button data-iap-buy="remove_ads_monthly"
        data-iap-label="Remove ads β€” {price}/month"
        data-iap-owned="βœ“ No more ads">
  Remove ads
</button>

<div data-iap-hide>
  Ads help keep this game free.
</div>

9Contoh lengkap - React atau Next.js#

CARA MUDAH: label yang persis sama berfungsi di React dan Next.js, karena hanya markup. Tanpa useState, tanpa useEffect, tanpa 'use client'. Tombol yang ditambahkan React belakangan diambil otomatis.

Itu semua yang dibutuhkan kebanyakan aplikasi - gunakan blok pertama di bawah dan berhenti di situ.

CARA TERKENDALI (blok kedua): hanya jika Anda ingin React sendiri yang menampilkan harga dan mengganti layar. Maka:

  • Di Next.js taruh 'use client' di baris 1 file, atau build gagal.
  • Hanya sentuh window.AppMintIAP di dalam useEffect - di badan komponen ia berjalan di server, tempat window tidak ada.
  • onChange(fn) langsung memberi Anda keadaan dan lagi setiap ada perubahan, serta mengembalikan fungsi berhenti berlangganan yang diinginkan React.
  • Di Next.js, build sebagai static export: output: 'export' di next.config.js.
// ── EASY WAY β€” just JSX, nothing else ──────────────────────────
export default function Shop() {
  return (
    <>
      <button data-iap-buy="level_pack_2"
              data-iap-label="Levels 11-20 β€” {price}"
              data-iap-owned="βœ“ Unlocked">Levels 11-20</button>

      <div data-iap-show="level_pack_2" hidden>πŸŽ‰ Levels 11-20 are yours!</div>
    </>
  );
}


// ── CONTROLLED WAY β€” React draws everything ───────────────────
'use client';                       // Next.js: must be line 1
import { useEffect, useState } from 'react';

export default function Shop() {
  const [iap, setIap] = useState({ available: false, products: {} });

  useEffect(() => {
    if (typeof window === 'undefined' || !window.AppMintIAP) return;
    window.AppMintIAP.start({
      plans: [{ id: 'level_pack_2' }, { id: 'remove_ads_monthly' }]
    });
    return window.AppMintIAP.onChange(setIap);
  }, []);

  if (!iap.available) return null;   // outside the app there is nothing to buy

  const levels = iap.products?.level_pack_2;   // always optional-chain
  return levels?.owned
    ? <p>πŸŽ‰ Levels 11-20 are yours!</p>
    : <button onClick={() => window.AppMintIAP.buy('level_pack_2')}>
        Levels 11-20 {levels?.price}
      </button>;
}

10Pemeriksaan cepat di browser Anda#

Buka halaman Anda di browser biasa (Chrome di PC atau ponsel).

Anda akan melihat tombol beli ada di sana tanpa melakukan apa-apa, dan bagian berbayar Anda tetap tersembunyi. Keduanya benar: di luar aplikasi tidak ada yang bisa dibeli, jadi tidak ada yang bisa dibeli secara tidak sengaja.

Di dalam aplikasi, halamannya sama sekali berbeda: tombol mendapat harga asli, membuka layar pembayaran Google, dan menyembunyikan diri setelah pembeli memilikinya. Situs web Anda tetap berfungsi normal bagi pengunjung biasa.

Ingin penawaran hilang sepenuhnya di web? Bungkus dalam <div data-iap-container hidden> - pembungkus itu hanya ditampilkan di dalam aplikasi.

11Langkah 3 - Nyalakan pembelian di STEPPER#

⚠️ JANGAN LEWATKAN INI. Jika dilewatkan, tombol Anda tetap muncul, tetapi setiap ketukan dijawab 'In-app purchases are not enabled in this app.' dan Anda tidak menerima uang.

STEPPER adalah layar Step 1 β†’ Step 2 β†’ Step 3 yang Anda gunakan untuk membuat aplikasi. Pembelian ada di STEPPER STEP 3.

LAKUKAN BERURUTAN:

  1. Buka stepper dan masuk ke STEPPER STEP 3 (layar dengan Ads, Notifications, dan Permissions).
STEPPER STEP 3 β€” what to set

  Ads ......................... ON  (needed to see the card below)
  πŸ’° Sell In-App Products ..... ON  ← REQUIRED for selling anything

  Selling ad removal too?
  Remove Ads (IAP) ............ ON
  Play Store Product ID ....... remove_ads_monthly
         (two plans) .......... remove_ads_monthly,remove_ads_lifetime
  License Key ................. paste from Play Console

  Export ...................... AAB  (not APK)

12Langkah 4 - Taruh aplikasi Anda di Google Play#

Di Play Console Anda (situs tempat pengembang mengelola aplikasi):

  1. 'Create app' β†’ isi nama dan pertanyaan dasar.
  2. Di menu kiri: 'Testing' β†’ 'Internal testing' β†’ 'Create new release' β†’ unggah file AAB Anda β†’ 'Save' lalu rilis.
  3. Masih di Internal testing β†’ tab 'Testers' β†’ tambahkan Gmail Anda sendiri β†’ salin tautan 'join' untuk nanti.

Internal testing adalah area privat - belum ada yang bisa melihat aplikasi Anda. Gunanya agar ANDA bisa mencoba pembelian sebelum dirilis publik.

13Langkah 5 - Buat produk Anda dan tetapkan harga#

Sekarang beri tahu Google apa yang Anda jual. Menu yang Anda gunakan bergantung pada apakah mereka membayar sekali atau setiap bulan.

BAYAR SEKALI (level, Pro, koin, hapus-iklan-selamanya)

  1. Play Console β†’ aplikasi Anda β†’ 'Monetize' β†’ 'Products' β†’ 'In-app products'.
  2. 'Create product'.
  3. Product ID: ketik PERSIS ID dari halaman Anda - level_pack_2. Ejaan sama, huruf besar-kecil sama. Google tidak pernah mengizinkan Anda mengubah atau memakainya ulang.
  4. Nama: yang dilihat pembeli, mis. 'Levels 11-20'. Deskripsi: satu baris.
  5. Tetapkan harga dalam mata uang Anda sendiri - Google mengonversinya untuk setiap negara.
  6. Tekan 'Activate'.

SETIAP BULAN (hapus iklan bulanan, langganan semua level)

  1. Play Console β†’ 'Monetize' β†’ 'Products' β†’ 'Subscriptions'.
  2. 'Create subscription'. Product ID: remove_ads_monthly.
  3. Simpan, lalu tambahkan BASE PLAN: tipe 'Auto-renewing', periode tagihan 'Monthly', harga Anda.
  4. Tekan 'Activate' pada base plan DAN pada langganannya sendiri. Lewatkan salah satunya dan aplikasi Anda tidak bisa melihatnya.

Ulangi untuk setiap ID yang Anda pakai. Kode halaman Anda tidak berubah - aplikasi menanyakan ke Google jenis setiap ID.

INGIN BULANAN *DAN* TAHUNAN? Buat DUA langganan terpisah (all_levels_monthly dan all_levels_yearly), bukan dua base plan dalam satu langganan. Satu langganan dengan beberapa base plan selalu menawarkan yang pertama, tanpa cara untuk memilih.

Ubah harga kapan saja di Play Console - aplikasi Anda menampilkan harga baru dengan sendirinya, tanpa perlu build ulang.

14Langkah 6 - Ujilah (Anda tidak akan ditagih)#

  1. Play Console β†’ menu kiri 'Setup' β†’ 'License testing' β†’ tambahkan Gmail Anda ke daftar penguji.
  2. Di ponsel Anda: buka tautan join internal-testing dari Langkah 4, terima, dan pasang aplikasi Anda DARI Play Store.
  3. Buka aplikasi. Tombol beli Anda muncul dengan harga asli.
  4. Ketuk - lembar pembayaran Google terbuka dan bertuliskan 'Test card, always approves'. Belilah. Konten pro Anda muncul. Anda tidak membayar apa pun.
  5. Pemeriksaan bonus: hapus lalu pasang ulang aplikasi - konten pro terbuka sendiri saat peluncuran. Itu Google memulihkan pembelian.

⚠️ Jika Anda memasang APK secara langsung (sideload), bukan dari Play Store, tombol beli TIDAK akan muncul. Itu normal - selalu uji lewat tautan Play Store.

15Langkah 7 - Tayang dan dapatkan bayaran#

  1. Jika pengujian sudah bagus: Play Console β†’ 'Production' β†’ buat rilis dengan AAB yang sama β†’ rilis. (Aplikasi pertama melalui peninjauan Google - biasanya beberapa hari.)
  2. Pengguna membeli di dalam aplikasi Anda β†’ Google menerima uangnya.
  3. Google membayar rekening bank Anda setiap bulan (Play Console β†’ 'Payments profile'). Google mengambil biaya layanan standarnya (15% untuk sebagian besar pengembang). AppMint tidak mengambil apa pun.

Untuk menambah produk nanti: tambahkan ID baru di kode halaman Anda, build ulang sekali, dan buat ID di Play Console. Perubahan harga tidak pernah perlu build ulang.

16Jika ada yang tidak berfungsi#

Aplikasi Anda memberi tahu apa yang salah, bukan gagal diam-diam. Temukan pesan yang Anda lihat:

'In-app purchases are not enabled in this app.'
β†’ 'πŸ’° Sell In-App Products' dalam keadaan MATI di STEPPER STEP 3 saat Anda membuat build. Nyalakan lalu build lagi.

'This item is not available on Google Play yet.'
β†’ Google tidak mengenali ID itu. Periksa huruf demi huruf dengan Play Console, pastikan produk AKTIF (untuk langganan, base plan DAN langganan harus keduanya diaktifkan), dan pastikan Anda memasang aplikasi dari tautan penguji Play.

'Google Play could not start the payment.'
β†’ Aplikasi tidak dipasang dari Play Store, atau ditandatangani dengan kunci yang berbeda dari yang Anda unggah.

'Payment pending - this unlocks automatically once Google confirms.'
β†’ Tidak perlu berbuat apa-apa. Metode pembayaran lambat (voucher tunai, beberapa UPI) butuh beberapa menit; pembukaan datang sendiri, bahkan setelah aplikasi dimulai ulang.

SAMA SEKALI TIDAK ADA PESAN, tidak terjadi apa-apa saat diketuk
β†’ Hampir selalu salah ketik di data-iap-buy, atau aplikasi tidak dipasang dari Play. Penyebab lain menampilkan salah satu pesan di atas.

PEMBAYARAN BERHASIL TAPI IKLAN MASIH ADA
β†’ ID produk itu tidak ada di STEPPER STEP 3 β†’ 'Remove Ads (IAP)' β†’ 'Play Store Product ID'. Tambahkan (pisahkan dengan koma jika beberapa) lalu build lagi.

TOMBOL BELI TIDAK PERNAH MUNCUL DI APLIKASI
β†’ 'πŸ’° Sell In-App Products' mati saat build. Di browser web biasa, tombolnya disembunyikan DENGAN SENGAJA - tidak ada yang bisa dibeli di luar aplikasi.

TIDAK ADA YANG TERJADI SETELAH SAYA MENUTUP LEMBAR PEMBAYARAN GOOGLE
β†’ Itu pembatalan biasa. Ketuk lagi dan selesaikan pembayaran.

HARGA TERLIHAT LAMA
β†’ Play menyimpan harga di perangkat selama beberapa jam. Instalasi baru selalu menampilkan harga terbaru.

17React, Next.js, atau JavaScript biasa#

REACT DAN NEXT.JS TIDAK MEMERLUKAN APA PUN YANG KHUSUS. Label di atas hanya markup, jadi satu baris yang sama berfungsi di sana juga - tanpa useState, tanpa useEffect, tanpa 'use client':

<button data-iap-buy="pro_unlock"

      data-iap-label="Unlock Pro β€” {price}">Unlock Pro</button>

Tombol yang dipasang React belakangan ditemukan otomatis, dan pemasangan ulang aman. Di Next.js, build sebagai static export (output: 'export').

Lebih suka kode daripada label? Satu baris melakukan hal yang sama:

AppMintIAP.sell('pro_unlock', '#buy-btn');

Argumen ketiga opsional: {label:'Unlock Pro - {price}', owned:'βœ“ Unlocked', show:'#thanks', hide:'.upsell'}. Panggil sekali per produk.

HANYA JIKA ANDA INGIN REACT MENAMPILKAN HARGA SENDIRI - jika tidak, lewati ini:

const [iap, setIap] = useState({ available: false, products: {} });

useEffect(() => {

if (typeof window === 'undefined' || !window.AppMintIAP) return;
window.AppMintIAP.start({ plans: [{ id: 'pro_unlock' }] });
return window.AppMintIAP.onChange(setIap);

}, []);

const pro = iap.products?.pro_unlock; // selalu gunakan optional chaining

if (!iap.available) return null; // tidak ada yang dibeli di luar aplikasi
if (pro?.owned) return <p>Terima kasih!</p>;
return <button onClick={() => window.AppMintIAP.buy('pro_unlock')}>

       Unlock Pro {pro?.price}
     </button>;

DUA KEBIASAAN MENCEGAH INI RUSAK:

  1. Baca kepemilikan PER PRODUK - iap.products?.<id>?.owned. iap.owned tingkat atas berarti 'ada yang dimiliki' dan maknanya berubah saat Anda menjual produk kedua.
  2. Gunakan optional chaining untuk seluruh path, agar field yang tidak terduga tidak pernah menimbulkan error saat React merender.

onChange langsung memberi Anda {available, adFree, owned, ownedIds, products} dan setiap ada perubahan, serta mengembalikan fungsi berhenti berlangganan - jadi pembeli yang kembali sudah terbuka sejak render pertama. Di Next.js tambahkan 'use client' di baris 1 dan build dengan output: 'export'.

Juga: AppMintIAP.available, .isOwned(id), .owned(), .buy(id), .state(), .refresh(). Label data-iap-buy juga berfungsi di JSX, dan tombol yang dipasang React belakangan diambil otomatis.

Ingin menjalankan bridge mentah sendiri? Semua di atas dibangun dari ini:

  • iap.isOwned(id) - true/false seketika, berfungsi offline
  • iap.getOwnedProducts() - array JSON berisi semua ID yang dimiliki
  • iap.getProducts(reqId, idsJson) - harga langsung; hasil datang sebagai event 'appmint:products' {requestId, products:[{productId, title, price, currency, type, owned}]}
  • iap.purchase(id) - produk sekali bayar DAN langganan (terdeteksi otomatis)
  • Event: 'appmint:purchase' {productId} (berikan akses HANYA di sini - ketukan pada sesuatu yang sudah dimiliki memicunya lagi), 'appmint:owned-changed' {products} (pemulihan saat peluncuran selesai, atau pengembalian dana mengambil sesuatu - baca ulang kepemilikan),'appmint:purchase-failed' {reason: disabled | not_found | billing_error | pending}

Hanya menjual satu peningkatan 'remove ads'? Gunakan pasangan bawaan yang lebih sederhana - aktifkan 'Remove Ads (IAP)' di Langkah 3 wizard:

var iap = window.WebToApk;

if (iap && !iap.isPremium()) {
  showRemoveAdsButton(function onTap() {
    iap.startRemoveAdsPurchase();
  });
}

window.addEventListener('appmint:premium', function (e) {
  if (e.detail.premium) hideRemoveAdsButton();
});

Remove Ads (IAP)

Penyiapan Google Play Billing

1Buat Managed Product#

Di dasbor Google Play Console Anda:

  • Buka 'Monetization' > 'In-app products'
  • Klik 'Create product'
  • Masukkan Product ID (mis. 'remove_ads_premium')
  • Tetapkan Nama (mis. 'Remove All Ads') dan Deskripsi
  • Tetapkan Harga lalu klik 'Save'
  • Klik 'Activate' agar tersedia untuk pengujian

Menjual penghapusan iklan sebagai LANGGANAN? Buat di 'Monetization' > 'Subscriptions' dengan base plan auto-renewing, dan aktifkan KEDUANYA base plan dan langganan. Bebas iklan lalu berlaku selama langganan aktif, dan iklan kembali otomatis saat berakhir.

2Dapatkan License Key Anda#

Google Play memerlukan kunci publik RSA untuk memverifikasi pembelian dengan aman.

Di Play Console:

  • Buka 'Monetization' > 'Monetization setup'
  • Di bawah 'Licensing', salin 'Base64-encoded RSA public key'
  • ⚠️ PENTING: Anda perlu menempelkan ini ke Generator nanti.
Example Key Format:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...

3Penyiapan di WebToApk Generator#

Di aplikasi generator:

  1. Aktifkan toggle 'Remove Ads (IAP)' di Langkah 3
  2. Masukkan 'Product ID' yang PERSIS sama dengan yang Anda buat di Langkah 1
  3. Tempel 'Base64 License Key' Anda dari Langkah 2
  4. Buat APK/AAB Anda

Menawarkan lebih dari satu paket bebas iklan (mis. langganan bulanan DAN pembukaan seumur hidup sekali bayar)? Taruh setiap Product ID di satu kolom ini, dipisahkan koma - memiliki SALAH SATUNYA menghapus iklan. ID pertama yang tercantum adalah yang dibeli oleh item menu 'Remove Ads' bawaan, jadi taruh paket default Anda di awal.

Product ID: remove_ads_premium

Two plans:
remove_ads_monthly,remove_ads_lifetime

4Siapkan License Testers#

Untuk menguji pembelian tanpa menghabiskan uang sungguhan:

  • Di Play Console, buka 'Setup' > 'License testing'
  • Tambahkan alamat Gmail Anda ke daftar 'License testers'
  • Pastikan 'License response' diatur ke 'RESPOND_NORMALLY'

5Uji di Internal atau Closed Track#

Pembelian Dalam Aplikasi HANYA berfungsi saat aplikasi dipasang lewat Play Store (track Testing):

  1. Unggah AAB yang Anda buat ke track 'Internal Testing' atau 'Closed Testing'
  2. Bagikan tautan join penguji ke akun uji Anda
  3. Unduh aplikasi dari Play Store
  4. Ketuk tombol 'Remove Ads' di aplikasi Anda untuk menguji alurnya!
Note: Purchases will show a 'Test instrument, always approves' message for license testers.

Resep kode

Langkah-langkah di atas mencakup dasar-dasarnya - empat label, halaman biasa lengkap, satu baris React. Resep ini mencakup yang belum: beberapa paket yang saling menggantikan, langganan yang berakhir, hook framework, bridge mentah secara lengkap, dan tabel referensi.

Satu aturan#

Pembelian diberikan hanya saat Google mengonfirmasinya. Bukan saat tombol diklik, bukan saat lembar terbuka, bukan saat pengguna kembali ke halaman Anda. Semua di bawah ini adalah cara berbeda untuk menyatakan satu aturan itu.

Runtime sudah menegakkannya: AppMintIAP membaca ulang kepemilikan dari bridge saat peluncuran, di setiap pembelian, di setiap pengembalian dana, dan setiap kali aplikasi dibuka lagi. Kepemilikan bertahan melewati instal ulang, ponsel baru, dan kondisi offline, karena tersimpan di akun Google pembeli, bukan di halaman Anda.

Jangan pernah membuka akses di click handler, dan jangan simpan hak akses hanya di localStorage. Penyimpanan browser yang dibersihkan tidak boleh merugikan pelanggan yang sudah membayar - dan nilai localStorage yang disalin tidak boleh memberikannya gratis.

Resep 1 - tabel harga dengan harga langsung#

Tiga paket di mana membeli salah satunya menggantikan yang lain. group yang membuatnya menjadi alternatif: tanpanya, mereka produk yang tidak berhubungan dan membeli paket seumur hidup akan membiarkan tombol bulanan tetap ada.

Tiga alternatif, satu grup
<div class="plans" data-iap-container>
  <button data-iap-buy="pass_monthly" data-iap-group="pass"
          data-iap-label="Monthly β€” {price}" data-iap-owned="Active">Monthly</button>

  <button data-iap-buy="pass_yearly" data-iap-group="pass"
          data-iap-label="Yearly β€” {price}" data-iap-owned="Active">Yearly</button>

  <button data-iap-buy="pass_lifetime" data-iap-group="pass"
          data-iap-label="Lifetime β€” {price}" data-iap-owned="Owned">Lifetime</button>
</div>

<p data-iap-show="pass">You are a member. Thank you.</p>
<p data-iap-hide="pass">Members get every level and no ads.</p>

ID produk yang tidak dikenal Google - belum dibuat, belum diaktifkan, atau aplikasi tidak dipasang dari Play Store - dibiarkan tanpa harga. Tombolnya sengaja tetap bisa diketuk: mengetuknya akan melaporkan alasan sebenarnya, bukan diam saja.

Resep 2 - React#

onChange terpicu langsung dengan keadaan saat ini dan lagi setiap ada perubahan, serta mengembalikan fungsi berhenti berlangganannya sendiri - jadi persis bentuk yang diinginkan useEffect.

hooks/useIap.js
import { useEffect, useState } from 'react';

/** Purchase state for the whole app. Safe in a browser too: available is false there. */
export function useIap() {
  const [iap, setIap] = useState({ available: false, ownedIds: [], products: {}, adFree: false });

  useEffect(() => {
    if (!window.AppMintIAP) return;             // running in a browser, or IAP not enabled
    return window.AppMintIAP.onChange(setIap);  // returns the unsubscribe fn
  }, []);

  return {
    ...iap,
    owns: (id) => iap.ownedIds.includes(id),
    priceOf: (id) => iap.products[id]?.price ?? null,
    buy: (id) => window.AppMintIAP?.buy(id)
  };
}
Gerbang di sekitar apa pun yang Anda jual
import { useIap } from './hooks/useIap';

export function ProGate({ children }) {
  const { available, owns, priceOf, buy } = useIap();

  if (owns('pro_unlock')) return children;

  return (
    <div className="upsell">
      <h2>Pro</h2>
      <p>Unlimited projects, no watermark.</p>
      <button onClick={() => buy('pro_unlock')} disabled={!available}>
        {available ? `Unlock Pro${priceOf('pro_unlock') ? ` β€” ${priceOf('pro_unlock')}` : ''}` : 'Not available here'}
      </button>
    </div>
  );
}

Tombol memanggil buy(); ia tidak membuka apa pun. Pembukaan terjadi karena Google mengonfirmasi, onChange terpicu, owns('pro_unlock') menjadi true, dan komponen dirender ulang. Itu aturan yang sama, diungkapkan dalam React.

Resep 3 - Vue#

Gerbang yang sama di Vue 3
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';

const iap = ref({ available: false, ownedIds: [], products: {} });
let stop;

onMounted(() => { stop = window.AppMintIAP?.onChange(s => (iap.value = s)); });
onUnmounted(() => stop && stop());

const isPro = computed(() => iap.value.ownedIds.includes('pro_unlock'));
const price = computed(() => iap.value.products.pro_unlock?.price ?? '');
</script>

<template>
  <slot v-if="isPro" />
  <button v-else :disabled="!iap.available" @click="AppMintIAP.buy('pro_unlock')">
    Unlock Pro {{ price }}
  </button>
</template>

Resep 4 - langganan#

Langganan dibeli persis seperti produk sekali bayar; bedanya ia bisa hilang. type memberi tahu jenis produknya dan period memberi interval penagihan sebagai durasi ISO-8601 - P1M bulanan, P1Y tahunan, P1W mingguan.

Pass bulanan yang kedaluwarsa dengan sendirinya
AppMintIAP.onChange(state => {
  const pass = state.products.season_pass;      // undefined until Google answers
  const active = state.ownedIds.includes('season_pass');

  document.body.classList.toggle('has-pass', active);

  if (pass && !active) {
    const every = { P1W: 'week', P1M: 'month', P1Y: 'year' }[pass.period] || 'period';
    label.textContent = `Season pass β€” ${pass.price} / ${every}`;
  }
});

// The pass lapsed (not renewed, or refunded): the same handler runs with it gone.
// Nothing to write β€” the class comes off and the paid content hides itself.

Anda tidak melakukan polling dan tidak perlu tanggal kedaluwarsa. Play memulihkan daftar yang dimiliki di setiap peluncuran, jadi langganan yang berakhir berhenti muncul di ownedIds.

Aktifkan baik langganan maupun base plan-nya di Play Console. Langganan dengan base plan tidak aktif tidak mengembalikan detail sama sekali, dan tombol akan melaporkan "not available on Google Play yet".

Resep 5 - bridge mentah, tanpa helper#

Semua di atas dibangun dari enam metode dan lima event. Jika Anda ingin mengendalikan UI sepenuhnya, gunakan langsung.

Seluruh permukaan, dalam satu file
const bridge = window.WebToApk;
const enabled = !!(bridge && bridge.purchase);          // false in a browser

// --- what does Google charge for these, in this buyer's currency? ---
const reqId = 'prices-' + Date.now();
window.addEventListener('appmint:products', function onProducts(e) {
  if (e.detail.requestId !== reqId) return;             // not our answer
  window.removeEventListener('appmint:products', onProducts);
  for (const p of e.detail.products) {
    // {productId, type:'inapp'|'subs', title, description, price, currency, period?, owned}
    render(p);
  }
});
bridge.getProducts(reqId, JSON.stringify(['pro_unlock', 'season_pass']));

// --- what does this buyer already own? instant, and works offline ---
const owned = JSON.parse(bridge.getOwnedProducts() || '[]');
const isPro = bridge.isOwned('pro_unlock');

// --- buy ---
buyButton.onclick = () => bridge.purchase('pro_unlock');

// --- the ONLY place a purchase is ever granted ---
window.addEventListener('appmint:purchase', e => {
  if (e.detail.productId === 'pro_unlock' && e.detail.owned) unlockPro();
});

// --- and the only place failures show up ---
window.addEventListener('appmint:purchase-failed', e => {
  toast({
    not_found:     'This item is not available on Google Play yet.',
    billing_error: 'Google Play could not start the payment. Please try again.',
    pending:       'Payment pending β€” this unlocks automatically once Google confirms.',
    disabled:      'In-app purchases are not enabled in this app.'
  }[e.detail.reason] || 'Purchase could not be completed. Please try again.');
});

// --- the launch-time restore finished, or a refund took something away ---
window.addEventListener('appmint:owned-changed', () => refreshEverything());

appmint:purchase terpicu lagi untuk sesuatu yang sudah dimiliki, dengan sengaja. Tulis unlockPro() agar menjalankannya dua kali tidak berbahaya dan Anda tidak perlu melacak apakah sudah pernah dijalankan.

Resep 6 - turunkan fungsi dengan jujur di luar aplikasi#

Halaman yang sama biasanya juga harus terbuka di browser biasa - saat pengembangan, atau karena Anda juga menerbitkannya di web. Di sana, window.WebToApk tidak ada dan tidak ada pembelian yang bisa terjadi. Katakan begitu, daripada menampilkan tombol yang tidak melakukan apa-apa.

Satu pemeriksaan, di bagian atas
const inApp = !!(window.AppMintIAP && window.AppMintIAP.available);

if (!inApp) {
  buyButton.disabled = true;
  buyButton.textContent = 'Available in the Android app';
  // and let people get it:
  storeLink.hidden = false;
}

Referensi#

Semua yang bisa dipanggil, didengarkan, atau ditandai oleh halaman.

window.AppMintIAP - helper yang disuntikkan runtime
PanggilFungsi
AppMintIAP.availableFalse di browser, atau saat pembelian dalam aplikasi tidak dinyalakan untuk build ini. Periksa sebelum menampilkan apa pun untuk dijual.
AppMintIAP.sell(id, button, opts)Hubungkan satu produk ke satu tombol. opts: label, owned, group, show, hide, labelEl. Panggil sekali per produk.
AppMintIAP.start(config)Hubungkan beberapa sekaligus: {plans, show, hide, container, toast, hideOtherPlans}. Gunakan jika Anda ingin fungsi toast sendiri, atau agar paket lain dalam grup tetap terlihat setelah satu dibeli (hideOtherPlans: false).
AppMintIAP.onChange(fn)Terpicu langsung dengan keadaan saat ini, lalu setiap ada perubahan. Mengembalikan fungsi berhenti berlangganan.
AppMintIAP.state()Objek yang sama, sekali: {available, adFree, owned, ownedIds, products}.
AppMintIAP.isOwned(id)True/false, seketika, offline.
AppMintIAP.owned()Array berisi setiap ID produk yang dimiliki.
AppMintIAP.buy(id)Membuka lembar pembelian Google. Tidak memberikan apa pun dengan sendirinya.
AppMintIAP.refresh()Pindai ulang tombol data-iap-buy yang ditambahkan sejak pemindaian terakhir. Jarang diperlukan - runtime memantau DOM.
window.WebToApk - bridge di bawahnya
PanggilMengembalikan
purchase(productId)Tidak ada. Membuka lembar pembayaran; hasilnya datang sebagai event.
getProducts(requestId, idsJson)Tidak ada. Jawabannya datang sebagai appmint:products yang membawa requestId.
getOwnedProducts()Array JSON berisi ID produk yang dimiliki, sebagai string.
isOwned(productId)Boolean - produk sekali bayar dimiliki, atau langganan sedang aktif.
isPremium()Boolean - khusus hak "remove ads".
startRemoveAdsPurchase()Tidak ada. Membuka lembar pembayaran untuk ID PERTAMA di kolom "Remove Ads" wizard; memiliki salah satu ID yang tercantum di sana menghapus iklan. Hasilnya datang sebagai appmint:premium.
Event
EventdetailKapan
appmint:purchase{productId, owned}Google mengonfirmasi. Satu-satunya tempat untuk membuka akses. Terpicu lagi untuk sesuatu yang sudah dimiliki.
appmint:purchase-failed{productId, reason}Lembar tidak pernah terbuka, alur mengalami kesalahan, atau pembayaran tertunda. Tidak pernah terpicu untuk pembatalan biasa.
appmint:products{requestId, products[]}Balasan atas panggilan getProducts(). ID yang tidak dikenal Play tidak akan ada.
appmint:premium{premium}Hak bebas iklan berubah - pembelian, pemulihan saat peluncuran, pengembalian dana, atau kedaluwarsa.
appmint:owned-changed{products[]}Pemulihan saat peluncuran selesai, atau pengembalian dana mengambil sesuatu. Membawa setiap ID yang dimiliki.
Atribut HTML
AtributAktifFungsi
data-iap-buy="id"tombolMembuatnya membeli produk itu.
data-iap-label="… {price}"tombolTemplat label. {price} menjadi harga lokal langsung dari Google.
data-iap-owned="…"tombolLabel setelah produk dimiliki.
data-iap-group="name"tombolMenandai paket sebagai alternatif satu sama lain.
data-iap-show="id|group"apa sajaHanya tampil jika itu dimiliki.
data-iap-hide="id|group"apa sajaTersembunyi setelah itu dimiliki.
data-iap-textdi dalam tombolElemen yang teksnya diberi label, jika tombol berisi lebih dari sekadar teks.
data-iap-containerpembungkusHanya tampil di dalam aplikasi; tersembunyi di browser, tempat tidak ada yang bisa dibeli. Bungkus seluruh penawaran di dalamnya.
data-iap-state="owned"diatur oleh runtimePada tombol produk yang dimiliki, agar Anda bisa menatanya.
Alasan kegagalan pada appmint:purchase-failed
reasonYang sebenarnya terjadi
not_foundGoogle tidak mengenal ID produk ini - belum dibuat, belum diaktifkan, atau aplikasi tidak dipasang dari Play Store.
billing_errorPlay tidak bisa memulai pembayaran. Biasanya sementara; pembeli sebaiknya mencoba lagi.
pendingMetode pembayaran lambat (tunai, transfer bank). Terbuka sendiri saat Google mengonfirmasi - jangan anggap ini kegagalan.
disabledPembelian dalam aplikasi tidak dinyalakan untuk build ini.
unknownLainnya.

Sebelum Anda rilis#

  1. Setiap ID produk di kode Anda ada di Play Console dan sudah diaktifkan - untuk langganan, base plan-nya juga.
  2. Build membawa ID yang sama, dan kunci lisensi RSA Base64 dari Monetization setup.
  3. Alamat Gmail Anda ada di Setup β†’ License testing, dengan respons diatur ke RESPOND_NORMALLY.
  4. Anda mengunggah AAB ke Internal atau Closed testing dan memasangnya dari tautan Play Store - APK hasil sideload tidak akan pernah bisa membeli apa pun.
  5. Anda membeli setiap produk sebagai penguji lisensi dan melihat "Test instrument, always approves".
  6. Anda menghapus lalu memasang ulang aplikasi, dan pembelian kembali dengan sendirinya.
  7. Anda mematikan jaringan dan konten berbayar masih terbuka.
  8. Anda mengembalikan dana pembelian uji di Play Console dan melihat aplikasi terkunci lagi.

Kesalahan yang merugikan uang#

  • Membuka akses di click handler. Siapa pun yang membuka lembar lalu keluar mendapat produk gratis.
  • Menyimpan hak akses hanya di localStorage. Menghapus data situs mengambil sesuatu yang dibayar pelanggan; menyalin nilai memberikannya gratis.
  • Menulis harga langsung di HTML Anda. Harganya akan salah di setiap negara lain dan usang sejak hari Anda mengubahnya. Gunakan {price} atau products[id].price.
  • Menjual barang fisik atau layanan dunia nyata lewat Play billing. Google mewajibkan sebaliknya: itu harus memakai pemroses pembayaran biasa. Untuk itulah panduan Stripe.
  • Menguji pada APK hasil sideload. Pembelian dalam aplikasi hanya berfungsi saat aplikasi dipasang dari Play Store. Tidak ada yang Anda tulis yang bisa mengubahnya.
  • Menganggap pending sebagai kegagalan. Pembeli membayar dengan metode yang lambat; produk terbuka sendiri saat Google mengonfirmasi.

Halaman ini dibuat dari Panduan Integrasi di dalam aplikasi itu sendiri, jadi isinya persis sama dengan build saat ini. Baca sebagai Markdown.

Diperiksa terhadap panduan yang dirilis pada 2026-10-22.