API jembatan JavaScript
Pembelian dan hak akses
Berjualan dari halaman Anda sendiri, dan menanyakan apa yang sudah dimiliki pembeli ini. Lihat panduan pembelian dalam aplikasi untuk kode di sekitarnya.
AppMint.revenuecat helper#
AppMint.revenuecat β RevenueCat subscriptions
Contoh
Langganan dan pembelian sekali beli lewat proyek RevenueCat Anda: penawaran, pembelian, pemulihan, dan hak akses. Pembelian berjalan lewat Google Play di aplikasi; RevenueCat mencatat tiap pembelian sebelum aplikasi menyelesaikannya.
Mengembalikan: setiap metode mengembalikan Promise yang selalu menghasilkan {ok: true, β¦} atau {ok: false, error, message?}. configure({appUserId?}), getCustomerInfo(), restore(), logIn(id), logOut() β {ok, customerInfo}; getOfferings() β {ok, current, all}; purchase(pkgOrProductId, {offeringId?, offerId?, consumable?}) β {ok, productId, transaction, customerInfo}; getAppUserId() β {ok, appUserId}. Memerlukan: langkah Integrasi β RevenueCat dengan kunci Google Play PUBLIK Anda (goog_β¦).
if (window.AppMint && AppMint.revenuecat) {
await AppMint.revenuecat.configure({ appUserId: currentUser && currentUser.id }); // omit for an anonymous user
var off = await AppMint.revenuecat.getOfferings();
if (off.ok && off.current) {
off.current.availablePackages.forEach((p) => addPlanButton(p.identifier, p.title, p.price, p.period));
}
}
async function buy(pkg) { // a package object from getOfferings, or its identifier
var r = await AppMint.revenuecat.purchase(pkg);
if (r.ok) unlock(r.customerInfo.entitlements.active);
else if (r.error === 'pending') alert('Payment pending β it will unlock when Google confirms it.');
else if (!r.cancelled) alert('Purchase failed: ' + (r.message || r.error));
}
var info = await AppMint.revenuecat.getCustomerInfo();
var isPro = info.ok && !!info.customerInfo.entitlements.active.pro;
window.addEventListener('appmint:revenuecat', (e) => render(e.detail.customerInfo)); // entitlements changed
document.getElementById('restore').onclick = () => AppMint.revenuecat.restore();Kaitkan pembelian dengan akun Anda sendiri:
if (window.AppMint && AppMint.revenuecat) {
var li = await AppMint.revenuecat.logIn(user.id); // after sign-in
if (li.ok) refreshPlan(li.customerInfo.entitlements.active);
document.getElementById('signout').onclick = async function () {
await AppMint.revenuecat.logOut();
showAnonymous();
};
}Tampilkan pesan yang jujur untuk setiap kegagalan:
async function buyPackage(pkg) {
var r = await AppMint.revenuecat.purchase(pkg);
if (r.ok) return unlock(r.customerInfo.entitlements.active);
if (r.cancelled || r.error === 'cancelled') return;
var text = {
not_configured: 'Purchases are not set up in this build',
billing_unavailable: 'Google Play billing is not available on this phone',
product_not_found: 'This plan is not available right now',
network: 'No connection - try again online'
}[r.error] || ('Purchase failed: ' + (r.message || r.error));
alert(text);
}Catatan: customerInfo = {appUserId, originalAppUserId, requestDate, managementUrl, entitlements: {active, all}, activeSubscriptions, allPurchasedProductIds, nonSubscriptionTransactions}; setiap hak akses {identifier, isActive, productIdentifier, expirationDate, latestPurchaseDate, willRenew, periodType, store, isSandbox}. Berikan {consumable: true} untuk koin/kredit agar dapat dibeli lagi; langganan dan pembukaan sekali beli diakui. Saat offline, getCustomerInfo menjawab info terakhir yang diketahui dengan stale: true; pembelian yang dilakukan saat RevenueCat tidak terjangkau tetap diselesaikan dan tanda terimanya dikirim pada configure/restore berikutnya (receiptQueued: true). Kesalahan: not_configured, network, revenuecat_error (dengan status, code, message), billing_unavailable, billing_error, product_not_found, offer_not_found, cancelled, pending, already_anonymous, foreign_origin. Siapkan koneksi akun layanan Google Play RevenueCat seperti yang diminta RevenueCat; jangan pernah menaruh kunci sk_ rahasia di dalam aplikasi. Pembelian AppMintIAP yang sudah ada tetap bekerja tanpa perubahan.
AppMintIAP.available helper#
window.AppMintIAP.available
Contoh
Sebuah properti (bukan fungsi): true saat halaman ini benar-benar dapat membeli lewat Google Play.
Mengembalikan: properti boolean. Memerlukan: π° Jual Produk Dalam Aplikasi. Tanpanya, window.AppMintIAP sama sekali tidak ada.
Periksa sebelum menampilkan apa pun untuk dijual, dan katakan dengan jelas saat pembelian tidak mungkin di sini:
var inApp = !!(window.AppMintIAP && window.AppMintIAP.available);
var buyBtn = document.getElementById('buy-pro');
if (!inApp) {
buyBtn.disabled = true;
buyBtn.textContent = 'Available in the Android app';
}Satu tombol upgrade untuk situs web dan aplikasi Anda: Google Play di dalam aplikasi, checkout Anda sendiri di web:
document.getElementById('upgrade').addEventListener('click', function () {
var iap = window.AppMintIAP;
if (iap && iap.available) {
iap.buy('pro_unlock'); // inside the app
} else if (window.WebToApk) {
showMessage('Upgrades are not available in this version of the app.');
} else {
location.href = '/checkout/pro'; // your website's own checkout
}
});Di React atau Next.js, baca di dalam efek (render server tidak punya window), sehingga tidak ada yang dirender untuk dibeli di luar aplikasi:
import { useEffect, useState } from 'react';
export function useCanBuy() {
const [canBuy, setCanBuy] = useState(false); // false during the server render
useEffect(() => {
setCanBuy(!!(window.AppMintIAP && window.AppMintIAP.available));
}, []);
return canBuy;
}
// const canBuy = useCanBuy();
// return canBuy ? <UpgradeButton /> : null;Catatan: window.AppMintIAP dipasang sebelum skrip pertama halaman Anda berjalan; hanya pada WebView yang lebih tua dari sekitar 2021 ia datang setelah halaman selesai dimuat, jadi kode yang juga harus berjalan di sana sebaiknya menunggunya (lihat pola whenIap di AppMintIAP.sell). Nilai yang sama ada di AppMintIAP.state().available. Membungkus penawaran dengan data-iap-container menyembunyikannya di luar aplikasi tanpa kode.
AppMintIAP.buy helper#
window.AppMintIAP.buy(id)
Contoh
Membuka lembar pembelian Google Play untuk sebuah produk, dari tombol Anda sendiri. Ini tidak memberikan apa pun dengan sendirinya.
Mengembalikan: tidak ada. Hasilnya datang sebagai appmint:purchase {productId, owned:true} (dan lewat AppMintIAP.onChange) atau appmint:purchase-failed {productId, reason}. Memerlukan: π° Jual Produk Dalam Aplikasi dan produk aktif di Play Console.
Gunakan saat Anda menggambar tombol sendiri; pembukaan berasal dari kepemilikan, bukan dari ketukan:
var btn = document.getElementById('buy-pro');
btn.addEventListener('click', function () {
if (!window.AppMintIAP || !window.AppMintIAP.available) { showMessage('Available in the Android app.'); return; }
btn.disabled = true;
window.AppMintIAP.buy('pro_unlock');
});
window.addEventListener('appmint:purchase', function (e) {
if (e.detail.productId === 'pro_unlock') unlockPro(); // must be safe to run twice
btn.disabled = false;
});
window.addEventListener('appmint:purchase-failed', function (e) {
btn.disabled = false;
showMessage(e.detail.reason === 'pending' ? 'Payment pending. It unlocks once Google confirms.' : 'Purchase failed: ' + e.detail.reason);
});
document.addEventListener('visibilitychange', function () { if (!document.hidden) btn.disabled = false; }); // cancel: no eventPemilih paket dengan satu tombol "Lanjutkan" yang membeli paket yang dipilih:
document.getElementById('continue').addEventListener('click', function () {
var choice = document.querySelector('input[name="plan"]:checked'); // value = product ID
var iap = window.AppMintIAP;
if (!choice || !iap || !iap.available) return;
if (iap.isOwned(choice.value)) { showMessage('You already have this plan.'); return; }
iap.buy(choice.value);
});
window.addEventListener('appmint:purchase', function () {
document.getElementById('plan-picker').hidden = true;
showMessage('Thanks! Your plan is active.');
});Mengetuk alat khusus Pro menjalankannya jika dimiliki, dan jika tidak membuka lembar pembelian:
document.querySelectorAll('[data-pro-tool]').forEach(function (btn) {
btn.addEventListener('click', function () {
var iap = window.AppMintIAP;
if (iap && iap.isOwned('pro_unlock')) { runTool(btn.getAttribute('data-pro-tool')); return; }
if (iap && iap.available) iap.buy('pro_unlock');
else showMessage('Pro tools are available in the Android app.');
});
});
window.addEventListener('appmint:purchase', function (e) {
if (e.detail.productId === 'pro_unlock') document.body.classList.add('pro');
});Catatan: buy() adalah WebToApk.purchase(id) polos: tanpa label sibuk, tanpa pemeriksaan grup. Tombol yang dihubungkan dengan sell(), start() atau data-iap-buy melakukan semua itu untuk Anda, jadi pilih itu.
AppMintIAP.isOwned helper#
window.AppMintIAP.isOwned(id)
Contoh
Memeriksa, seketika dan secara offline, apakah pengguna memiliki sebuah produk (produk sekali beli, atau langganan yang masih aktif).
Mengembalikan: true / false, secara sinkron; false di luar aplikasi. Memerlukan: π° Jual Produk Dalam Aplikasi.
function paintPro() {
var pro = !!(window.AppMintIAP && window.AppMintIAP.isOwned('pro_unlock'));
document.getElementById('pro-tools').hidden = !pro;
}
// Re-check whenever ownership can change.
window.addEventListener('appmint:purchase', paintPro);
window.addEventListener('appmint:owned-changed', paintPro);
// AppMintIAP arrives after the page has loaded: paint once it is there.
var waitIap = setInterval(function () {
if (window.AppMintIAP) { clearInterval(waitIap); paintPro(); }
}, 200);
setTimeout(function () { clearInterval(waitIap); }, 10000); // not in the app, or selling is offPro lewat salah satu dari beberapa paket: produk bulanan, tahunan, dan seumur hidup semuanya dihitung:
var PRO_PLANS = ['pro_monthly', 'pro_yearly', 'pro_lifetime'];
function isPro() {
var iap = window.AppMintIAP;
return !!iap && PRO_PLANS.some(function (id) { return iap.isOwned(id); });
}
function paintPlan() { document.body.classList.toggle('pro', isPro()); }
paintPlan();
window.addEventListener('appmint:purchase', paintPlan);
window.addEventListener('appmint:owned-changed', paintPlan); // an expired subscription or a refundPenjaga rute: layar khusus Pro mengirim pengguna tanpa Pro ke layar upgrade:
function route() {
var path = location.hash.slice(1) || '/';
var proOnly = path.indexOf('/reports') === 0;
if (proOnly && !(window.AppMintIAP && window.AppMintIAP.isOwned('pro_unlock'))) {
location.replace('#/upgrade');
return;
}
render(path);
}
window.addEventListener('hashchange', route);
route();Catatan: Jawaban sama dengan WebToApk.isOwned(id). Kepemilikan dipulihkan dari akun Google pengguna di setiap peluncuran dan disimpan di ponsel, jadi tetap ada setelah pasang ulang dan bekerja offline. Jangan pernah menyimpan hak akses hanya di localStorage.
AppMintIAP.onChange helper#
window.AppMintIAP.onChange(fn)
Contoh
Memanggil fungsi Anda dengan status pembelian segera, dan lagi setiap kali berubah (harga tiba, pembelian masuk, pemulihan saat peluncuran, pengembalian dana, langganan kedaluwarsa).
Mengembalikan: fungsi berhenti berlangganan. fn menerima {available, adFree, owned, ownedIds, products}; products adalah {id: {price, currency, type, period, owned}} untuk produk yang dikenal helper (dari sell, start atau data-iap-buy). Memerlukan: π° Jual Produk Dalam Aplikasi.
Sebuah hook React. Ia mengembalikan fungsi berhenti berlangganan langsung dari efek:
import { useEffect, useState } from 'react';
type IapState = { available: boolean; adFree: boolean; owned: boolean; ownedIds: string[];
products: Record<string, { price: string | null; currency: string | null; type: string | null; period: string | null; owned: boolean }> };
export function useIap(ids: string[]) {
const [iap, setIap] = useState<IapState>({ available: false, adFree: false, owned: false, ownedIds: [], products: {} });
useEffect(() => {
let stop: (() => void) | undefined;
const t = setInterval(() => { // AppMintIAP arrives after page load
const api = (window as any).AppMintIAP;
if (!api) return;
clearInterval(t);
ids.forEach((id) => api.sell(id, null)); // track them, so their prices are fetched
stop = api.onChange(setIap);
}, 200);
return () => { clearInterval(t); if (stop) stop(); };
}, []);
return iap;
}
// const iap = useIap(['pro_unlock']);
// const isPro = iap.ownedIds.includes('pro_unlock');
// const price = iap.products.pro_unlock?.price;Paywall JavaScript biasa: lacak dua paket tanpa tombol, lalu gambar harga dan paket saat ini dari setiap perubahan:
var iap = window.AppMintIAP;
var PLANS = ['pro_monthly', 'pro_yearly'];
if (iap) {
iap.start({ plans: PLANS.map(function (id) { return { id: id }; }) }); // tracked, not drawn
iap.onChange(function (s) {
document.getElementById('paywall').hidden = !s.available;
PLANS.forEach(function (id) {
var el = document.querySelector('[data-plan="' + id + '"]');
var p = s.products[id];
el.hidden = !p; // Google has not answered for it (yet)
if (p) el.querySelector('.price').textContent = p.owned ? 'Your plan' : p.price;
});
});
}Berlangganan saat layar akun terbuka dan berhenti berlangganan saat ditutup. adFree dan ownedIds terisi setelah helper berjalan (halaman memakai sell(), start() atau data-iap-buy):
var stopAccount = null;
function openAccountScreen() {
if (window.AppMintIAP) {
stopAccount = window.AppMintIAP.onChange(function (s) {
document.getElementById('ad-status').textContent = s.adFree ? 'Ad-free' : 'With ads';
document.getElementById('owned-count').textContent = s.ownedIds.length + ' purchase(s)';
});
}
showScreen('account');
}
function closeAccountScreen() {
if (stopAccount) { stopAccount(); stopAccount = null; } // no updates for a hidden screen
showScreen('home');
}Catatan: Baca kepemilikan per produk (ownedIds atau products[id].owned). owned tingkat atas hanya berarti "ada yang dimiliki". adFree adalah hak akses Hapus Iklan (WebToApk.isPremium()). Sebuah produk ada di products hanya setelah Google menjawab untuknya.
AppMintIAP.owned helper#
window.AppMintIAP.owned()
Contoh
Mencantumkan setiap ID produk yang dimiliki pengguna.
Mengembalikan: array ID produk, secara sinkron, misalnya ['pro_unlock', 'level_pack_2']; [] di luar aplikasi atau saat tidak ada yang dimiliki. Memerlukan: π° Jual Produk Dalam Aplikasi.
function unlockOwnedLevels() {
var ids = window.AppMintIAP ? window.AppMintIAP.owned() : [];
document.querySelectorAll('[data-level-pack]').forEach(function (el) {
el.classList.toggle('locked', ids.indexOf(el.getAttribute('data-level-pack')) === -1);
});
}
window.addEventListener('appmint:owned-changed', unlockOwnedLevels); // restore or refund changed the list
window.addEventListener('appmint:purchase', unlockOwnedLevels);Hitung berapa level yang terbuka saat setiap paket level adalah produknya sendiri:
function unlockedLevelCount() {
var ids = window.AppMintIAP ? window.AppMintIAP.owned() : [];
var packs = ids.filter(function (id) { return /^level_pack_\d+$/.test(id); }).length;
return 10 + packs * 10; // 10 free levels, 10 more per pack
}
function paintLevelCount() {
document.getElementById('levels-label').textContent = unlockedLevelCount() + ' levels unlocked';
}
paintLevelCount();
window.addEventListener('appmint:owned-changed', paintLevelCount);Daftar "Pembelian saya" di layar pengaturan Anda, dengan nama Anda sendiri untuk setiap produk:
var LABELS = { pro_unlock: 'Pro features', remove_ads: 'No ads', level_pack_2: 'Levels 11-20' };
function renderPurchases() {
var ids = window.AppMintIAP ? window.AppMintIAP.owned() : [];
var list = document.getElementById('purchases');
list.textContent = '';
ids.forEach(function (id) {
var li = document.createElement('li');
li.textContent = LABELS[id] || id;
list.appendChild(li);
});
document.getElementById('no-purchases').hidden = ids.length > 0;
}
renderPurchases();
window.addEventListener('appmint:owned-changed', renderPurchases);Catatan: Di-parse dari WebToApk.getOwnedProducts() untuk Anda. Termasuk langganan aktif dan produk Hapus Iklan. Bekerja offline.
AppMintIAP.refresh helper#
window.AppMintIAP.refresh()
Contoh
Mencari lagi tombol data-iap-buy yang ditambahkan sejak pemindaian terakhir, dan menghubungkannya (atau memulai helper jika belum menemukan satu pun).
Mengembalikan: objek AppMintIAP. Memerlukan: π° Jual Produk Dalam Aplikasi.
Helper sudah memantau halaman untuk tombol data-iap-buy baru, jadi Anda jarang membutuhkan ini. Panggil setelah Anda menyisipkan tombol dengan cara yang bisa terlewat oleh pemantau, misalnya dengan mengubah atribut pada elemen yang sudah ada:
function showShop() {
var btn = document.getElementById('buy-coins');
btn.setAttribute('data-iap-buy', 'coins_500'); // attribute added to an EXISTING node
btn.setAttribute('data-iap-label', '500 coins β {price}');
if (window.AppMintIAP) window.AppMintIAP.refresh();
}Kartu toko yang produknya berasal dari server Anda sendiri: tombolnya sudah ada di halaman, hanya atributnya yang berubah, jadi hubungkan dengan refresh():
fetch('/shop.json').then(function (r) { return r.json(); }).then(function (shop) {
shop.items.forEach(function (item) { // [{slot, productId, name}]
var btn = document.querySelector('.shop-card[data-slot="' + item.slot + '"] button');
if (!btn) return;
btn.setAttribute('data-iap-buy', item.productId);
btn.setAttribute('data-iap-label', item.name + ' - {price}');
});
if (window.AppMintIAP) window.AppMintIAP.refresh();
});Paket liburan yang diobral pada waktu tertentu, pada tombol yang sudah ada di halaman sejak awal:
var saleStart = new Date('2026-12-01T00:00:00').getTime();
function openSale() {
var btn = document.getElementById('sale-btn');
btn.setAttribute('data-iap-buy', 'holiday_bundle');
btn.setAttribute('data-iap-owned', 'Bundle unlocked');
document.getElementById('sale-card').hidden = false;
if (window.AppMintIAP) window.AppMintIAP.refresh();
}
var wait = saleStart - Date.now();
if (wait <= 0) openSale();
else if (wait < 86400000) setTimeout(openSale, wait); // later than a day: the next launch opens itCatatan: Setiap tombol baru mendapat harganya dari Google dan status kepemilikannya. Tombol yang sudah terhubung tidak dihubungkan dua kali. Tombol yang elemennya dihapus dari halaman akan dilepas.
AppMintIAP.sell helper#
window.AppMintIAP.sell(id, button, opts)
Contoh
Menghubungkan satu produk ke satu tombol dalam satu baris. Panggil sekali per produk.
Mengembalikan: objek AppMintIAP (untuk perangkaian). Memerlukan: π° Jual Produk Dalam Aplikasi di wizard build dan produk yang dibuat dan aktif di Play Console. window.AppMintIAP dipasang sebelum skrip pertama halaman Anda berjalan (pada WebView yang lebih tua dari sekitar 2021, setelah halaman selesai dimuat - pola whenIap di bawah mencakup keduanya).
function whenIap(fn) {
if (window.AppMintIAP) { fn(window.AppMintIAP); return; }
var tries = 0;
var t = setInterval(function () {
if (window.AppMintIAP) { clearInterval(t); fn(window.AppMintIAP); }
else if (++tries > 50) clearInterval(t); // not in the app, or selling is off
}, 200);
}
whenIap(function (iap) {
iap.sell('pro_unlock', '#buy-pro', {
label: 'Unlock Pro β {price}', // {price} = Google's live, localized price
owned: 'β Unlocked', // the button text once owned
show: '#thanks', // shown once owned
hide: '.upsell' // hidden once owned
});
iap.sell('coins_500', '#buy-coins', { label: '500 coins β {price}' });
});Beberapa paket di mana membeli satu menggantikan yang lain berbagi group:
whenIap(function (iap) {
iap.sell('pass_monthly', '#monthly', { label: 'Monthly β {price}', group: 'pass' })
.sell('pass_yearly', '#yearly', { label: 'Yearly β {price}', group: 'pass' });
});Lacak produk tanpa tombol dan gambar sendiri penawarannya (null tidak menghubungkan apa-apa; tombol Anda memanggil buy):
whenIap(function (iap) {
iap.sell('remove_ads', null); // tracked, never drawn by the helper
iap.onChange(function (s) {
var p = s.products.remove_ads; // undefined until Google answers
var btn = document.getElementById('remove-ads');
if (!s.available || (p && p.owned)) { btn.hidden = true; return; }
btn.hidden = false;
btn.textContent = p && p.price ? 'Remove ads for ' + p.price : 'Remove ads';
});
document.getElementById('remove-ads').addEventListener('click', function () {
iap.buy('remove_ads');
});
});Hubungkan tombol yang baru ada setelah dialog toko Anda terbuka (panggilan yang terlambat bergabung ke helper yang berjalan):
document.getElementById('open-shop').addEventListener('click', function () {
openShopDialog(); // your code: adds <button id="buy-levels">
if (!window.AppMintIAP) return; // not in the app
window.AppMintIAP.sell('level_pack_2', '#buy-levels', {
label: 'Levels 11-20 β {price}',
owned: 'β Levels 11-20 unlocked',
show: '#levels-2-link' // your menu entry, shown once owned
});
});Catatan: button adalah elemen atau selektor CSS; berikan null untuk melacak produk tanpa tombol (UI Anda sendiri membaca AppMintIAP.state()). opts lainnya: labelEl (elemen di dalam tombol yang teksnya berubah). Ini bekerja sebelum atau sesudah helper dimulai, termasuk dari efek React. Sama dengan atribut data-iap-buy (lihat AppMintIAP.start). Buka konten berbayar hanya dari kepemilikan (isOwned, onChange), jangan pernah di handler klik Anda sendiri.
AppMintIAP.start helper#
window.AppMintIAP.start(config) and the data-iap-* attributes
Contoh
Menghubungkan tombol beli Anda ke Google Play dalam satu langkah: harga langsung di tombol, lembar pembelian saat diketuk, label "dimiliki", blok terima kasih, pemulihan saat peluncuran, dan pesan yang terlihat untuk setiap kegagalan.
Mengembalikan: objek AppMintIAP (untuk perangkaian). Memerlukan: π° Jual Produk Dalam Aplikasi di wizard build dan ID produk yang dibuat dan aktif di Play Console. Aplikasi memasang window.AppMintIAP sebelum skrip pertama halaman Anda berjalan (pada WebView yang lebih tua dari sekitar 2021, hanya setelah halaman selesai dimuat).
Tanpa kode sama sekali: tandai HTML-nya. Helper membaca atribut ini sendiri begitu disuntikkan, dan memantau tombol yang dirender React atau Vue belakangan:
<div data-iap-container> <!-- shown only inside the app -->
<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_lifetime" data-iap-group="pass"
data-iap-label="Lifetime β {price}" data-iap-owned="Owned">
<img src="star.svg" alt=""> <span data-iap-text>Lifetime</span> <!-- only this text changes -->
</button>
<button data-iap-buy="coins_500" data-iap-label="500 coins β {price}">500 coins</button>
</div>
<p data-iap-show="pass" hidden>You are a member. Thank you!</p> <!-- shown when any "pass" plan is owned -->
<p data-iap-hide="pass">Members get every level and no ads.</p> <!-- hidden once it is owned -->Dengan kode: panggil start() sendiri saat Anda ingin fungsi pesan atau hook pembukaan sendiri. Gunakan di halaman TANPA tombol data-iap-buy di HTML (dengan tombol itu, helper sudah berjalan dan start kedua tidak melakukan apa-apa):
function whenIap(fn) { // AppMintIAP arrives after page load
if (window.AppMintIAP) { fn(window.AppMintIAP); return; }
var tries = 0;
var t = setInterval(function () {
if (window.AppMintIAP) { clearInterval(t); fn(window.AppMintIAP); }
else if (++tries > 50) clearInterval(t); // not in the app, or selling is off
}, 200);
}
whenIap(function (iap) {
iap.start({
plans: [
{ id: 'pro_unlock', button: '#buy-pro', label: 'Unlock Pro β {price}', owned: 'β Pro' },
{ id: 'coins_500', button: '#buy-coins', label: '500 coins β {price}' }
],
show: '#pro-area', // shown once anything is owned
hide: '.upsell', // hidden once anything is owned
busyLabel: 'Opening Google Playβ¦',
onUnlock: function (id) { console.log('now owned:', id); },
onLock: function (id) { console.log('no longer owned (refund or expiry):', id); },
toast: function (msg) { showMessage(msg); } // failure messages; default: window.showToast, else alert
});
});Di React: mulai dengan paket tanpa button (tidak ada yang digambar) dan biarkan React merender dari onChange, yang mengembalikan fungsi berhenti berlangganan untuk efek:
function ProButton() {
const [iap, setIap] = React.useState({ available: false, products: {} });
React.useEffect(() => {
if (typeof window === 'undefined' || !window.AppMintIAP) return; // server render / browser
window.AppMintIAP.start({ plans: [{ id: 'pro_unlock' }] }); // safe to run twice
return window.AppMintIAP.onChange(setIap);
}, []);
const pro = iap.products?.pro_unlock; // read ownership per product
if (!iap.available) return null; // nothing to buy outside the app
return pro?.owned
? <p>Thanks! Pro is unlocked.</p>
: <button onClick={() => window.AppMintIAP.buy('pro_unlock')}>Unlock Pro {pro?.price}</button>;
}Catatan: Atribut: data-iap-buy="id", data-iap-label ({price} menjadi harga langsung Play), data-iap-owned (label setelah dimiliki; bawaan "β Dibeli"), data-iap-group (paket yang saling menggantikan; setelah satu dimiliki yang lain disembunyikan kecuali hideOtherPlans: false), data-iap-show / data-iap-hide (nilai = id produk atau grup; kosong = apa pun yang dimiliki), data-iap-text (bagian tombol yang teksnya berubah), data-iap-container. Tombol yang dimiliki mendapat data-iap-state="owned" dan kelas iap-unlocked. Pembatalan di lembar Google tidak mengirim event; tombol dilepas saat halaman terlihat lagi (atau setelah 30 dtk). Memanggil start(options) setelah helper sudah berjalan sendiri dari atribut data-iap-buy MENGGABUNGKAN opsi (paket, tampil/sembunyi, kontainer, onUnlock/onLock, toast, busyLabel, hideOtherPlans) ke set yang berjalan; memanggilnya dua kali tidak berbahaya. Halaman yang menyertakan appmint-iap.js sendiri tetap memakainya - salinan bawaan mengalah.
AppMintIAP.started helper#
window.AppMintIAP.started
Contoh
Sebuah properti (bukan fungsi): true setelah helper berjalan, yaitu telah menghubungkan setidaknya satu produk dari tombol start(), sell() atau data-iap-buy.
Mengembalikan: properti boolean. Memerlukan: π° Jual Produk Dalam Aplikasi.
Gunakan untuk memilih antara start() (dengan opsi Anda sendiri) dan sell() (yang juga bekerja pada helper yang sedang berjalan):
function setUpShop(iap) {
if (!iap.started) {
iap.start({
plans: [{ id: 'pro_unlock', button: '#buy-pro', label: 'Unlock Pro β {price}' }],
toast: function (msg) { showMessage(msg); }
});
} else {
// Already running (the page had data-iap-buy buttons): start() would do nothing now.
iap.sell('pro_unlock', '#buy-pro', { label: 'Unlock Pro β {price}' });
}
}
if (window.AppMintIAP) setUpShop(window.AppMintIAP);Beri peringatan pada diri sendiri selama pengembangan saat tidak ada yang terhubung di halaman (salah ketik di data-iap-buy, atau tombol tidak pernah dirender):
window.addEventListener('load', function () {
setTimeout(function () {
var iap = window.AppMintIAP;
if (iap && !iap.started) {
console.warn('AppMintIAP is still waiting: no data-iap-buy button, sell() or start() on this page');
}
}, 3000);
});Bedakan layar toko dari "masih memuat" dan "pembelian tidak tersedia":
function storeStatus() {
var iap = window.AppMintIAP;
if (!iap) return 'web'; // opened in a normal browser
if (!iap.started) return 'waiting'; // no product wired yet
return iap.state().available ? 'ready' : 'unavailable';
}
document.getElementById('store').setAttribute('data-status', storeStatus());Catatan: start() kedua tidak pernah memulainya dua kali (React StrictMode menjalankan efek dua kali); opsinya digabungkan ke set yang berjalan. Nilainya tetap false selama belum ada produk yang terhubung; helper lalu terus memantau halaman untuk tombol data-iap-buy.
AppMintIAP.state helper#
window.AppMintIAP.state()
Contoh
Mengembalikan status pembelian saat ini sekali, tanpa berlangganan.
Mengembalikan: {available, adFree, owned, ownedIds, products}, secara sinkron. ownedIds adalah array ID produk; products adalah {id: {price, currency, type: 'inapp'|'subs', period, owned}} untuk produk yang telah ditanyakan helper ke Google. Memerlukan: π° Jual Produk Dalam Aplikasi.
function describePass() {
if (!window.AppMintIAP) return 'Available in the Android app';
var s = window.AppMintIAP.state();
if (s.ownedIds.indexOf('season_pass') !== -1) return 'Your season pass is active';
var p = s.products.season_pass; // undefined until Google answers
if (!p || !p.price) return 'Season pass';
var every = { P1W: 'week', P1M: 'month', P1Y: 'year' }[p.period] || 'period';
return 'Season pass β ' + p.price + ' / ' + every;
}
document.getElementById('pass-label').textContent = describePass();Periksa kepemilikan saat pemain mengetuk level terkunci, dan arahkan mereka ke toko Anda jika belum memiliki:
function openLevel(n) {
var s = window.AppMintIAP ? window.AppMintIAP.state() : null;
var needsPack = n > 10;
if (needsPack && !(s && s.ownedIds.indexOf('level_pack_2') !== -1)) {
showShop('level_pack_2'); // your own screen
return;
}
startLevel(n);
}Sembunyikan ajakan "bebas iklan" Anda sendiri setelah hak akses bebas iklan build aktif, dan daftarkan harga yang sejauh ini dikirim Google:
if (window.AppMintIAP) {
var s = window.AppMintIAP.state();
document.querySelectorAll('.go-ad-free').forEach(function (el) { el.hidden = s.adFree; });
var list = document.getElementById('price-list');
Object.keys(s.products).forEach(function (id) {
var p = s.products[id];
var li = document.createElement('li');
li.textContent = id + ': ' + (p.owned ? 'owned' : (p.price || 'β¦')) +
(p.type === 'subs' ? ' (subscription)' : '');
list.appendChild(li);
});
}Catatan: Ini adalah snapshot. Untuk mengikuti perubahan gunakan AppMintIAP.onChange(fn), yang memberi objek yang sama setiap kali ada perubahan. owned bernilai true saat ada apa pun yang dimiliki; periksa produk dengan ownedIds atau AppMintIAP.isOwned(id).
getOwnedProducts jembatan#
window.WebToApk.getOwnedProducts(): String
Array JSON dari setiap ID produk yang dimiliki - untuk membatasi seluruh layar saat startup.
Contoh
Mencantumkan setiap produk Play yang dimiliki pengguna, untuk membuka seluruh layar saat mulai.
Mengembalikan: string JSON (array ID produk), secara sinkron, misalnya '["pro_unlock","level_pack_2"]'. Parse dengan JSON.parse. '[]' saat tidak ada yang dimiliki. Memerlukan: π° Jual Produk Dalam Aplikasi di wizard build.
function ownedIds() {
var b = window.WebToApk;
if (!b || typeof b.getOwnedProducts !== 'function') return [];
try { return JSON.parse(b.getOwnedProducts()); } catch (e) { return []; }
}
function unlockAll(ids) {
ids.forEach(function (id) {
var el = document.querySelector('[data-product="' + id + '"]');
if (el) el.classList.add('unlocked');
});
}
unlockAll(ownedIds());
// The owned list changed (launch restore, a new purchase, a refund): the full new list.
window.addEventListener('appmint:owned-changed', function (e) {
document.querySelectorAll('.unlocked').forEach(function (el) { el.classList.remove('unlocked'); });
unlockAll(e.detail.products); // e.detail = {products: ['pro_unlock', ...]}
});Perubahan yang sama juga memanggil window.onAppMintOwnedChanged(detail) jika Anda mendefinisikannya.
Daftar "Pembelian saya" di layar pengaturan Anda, dengan nama Anda sendiri untuk setiap produk:
var NAMES = { pro_unlock: 'Pro features', level_pack_2: 'Levels 11-20', remove_ads: 'No ads' };
function renderMyPurchases() {
var b = window.WebToApk, ids = [];
if (b && typeof b.getOwnedProducts === 'function') {
try { ids = JSON.parse(b.getOwnedProducts()); } catch (e) {}
}
var list = document.getElementById('my-purchases');
list.textContent = '';
ids.forEach(function (id) {
var li = document.createElement('li');
li.textContent = NAMES[id] || id;
list.appendChild(li);
});
document.getElementById('nothing-bought').hidden = ids.length > 0;
}
renderMyPurchases();
window.addEventListener('appmint:owned-changed', renderMyPurchases);Tentukan tingkat terbaik yang dimiliki pengguna saat setiap tingkat adalah produknya sendiri:
var TIERS = ['premium_plus', 'premium', 'basic']; // best first
function currentTier() {
var b = window.WebToApk, owned = [];
if (b && typeof b.getOwnedProducts === 'function') {
try { owned = JSON.parse(b.getOwnedProducts()); } catch (e) {}
}
for (var i = 0; i < TIERS.length; i++) if (owned.indexOf(TIERS[i]) !== -1) return TIERS[i];
return 'free';
}
applyTier(currentTier());
window.addEventListener('appmint:purchase', function () { applyTier(currentTier()); });
window.addEventListener('appmint:owned-changed', function () { applyTier(currentTier()); });Catatan: Termasuk produk sekali beli dan langganan aktif, dan juga produk Hapus Iklan jika pengguna membelinya. Bekerja offline; daftar disimpan di ponsel pada setiap pemulihan peluncuran dan pembelian. appmint:owned-changed hanya aktif jika daftar benar-benar berubah, jadi mungkin tidak aktif sama sekali pada peluncuran biasa.
getProducts jembatan#
window.WebToApk.getProducts(requestId: String, productIdsJson: String)
Meminta Play untuk detail terkini dari ID produk yang diberikan (array string JSON). Hasil datang sebagai 'appmint:products' {requestId, products:[{productId, type, title, description, price, currency, period?, owned}]}. ID yang tidak dikenal Play tidak ada dalam hasil - halaman menyembunyikan item tersebut.
Contoh
Meminta Google Play untuk detail terkini produk Anda: judul, deskripsi, dan harga yang dilokalkan.
Mengembalikan: tidak ada saat ini. Jawabannya datang sebagai event appmint:products: {requestId, products: [{productId, type, title, description, price, currency, period?, owned}]}. type adalah 'inapp' atau 'subs'; period adalah periode ISO-8601 seperti 'P1M' (hanya langganan). Memerlukan: π° Jual Produk Dalam Aplikasi di wizard build. Argumen kedua adalah string JSON: gunakan JSON.stringify.
function loadPrices(ids) {
return new Promise(function (resolve) {
var b = window.WebToApk;
if (!b || typeof b.getProducts !== 'function') { resolve([]); return; } // browser
var requestId = 'prices-' + Date.now();
window.addEventListener('appmint:products', function handler(e) {
if (String(e.detail.requestId) !== requestId) return; // another call's answer
window.removeEventListener('appmint:products', handler);
resolve(e.detail.products || []);
});
b.getProducts(requestId, JSON.stringify(ids));
});
}
loadPrices(['pro_unlock', 'coins_500', 'pro_monthly']).then(function (products) {
products.forEach(function (p) {
var btn = document.querySelector('[data-product="' + p.productId + '"]');
if (!btn) return;
btn.hidden = false;
btn.textContent = p.owned ? 'Owned' : p.title + ' Β· ' + p.price + (p.period === 'P1M' ? ' / month' : '');
});
});Jawabannya juga diteruskan ke window.onAppMintProducts(detail) jika Anda mendefinisikannya.
Pemilih paket langganan, setiap kartu diberi label harga Play dan periode penagihannya:
var PERIODS = { P1W: 'week', P1M: 'month', P3M: '3 months', P6M: '6 months', P1Y: 'year' };
window.addEventListener('appmint:products', function (e) {
if (e.detail.requestId !== 'plans') return;
e.detail.products.forEach(function (p) {
var card = document.querySelector('.plan[data-product="' + p.productId + '"]');
if (!card || p.type !== 'subs') return;
card.hidden = false;
card.querySelector('.price').textContent = p.price + ' / ' + (PERIODS[p.period] || p.period);
card.classList.toggle('current', p.owned);
});
});
var b = window.WebToApk;
if (b && typeof b.getProducts === 'function') {
b.getProducts('plans', JSON.stringify(['pro_weekly', 'pro_monthly', 'pro_yearly']));
}Saat menguji, catat ID yang tidak dikembalikan Google Play, lewat hook halaman:
var WANTED = ['pro_unlock', 'coins_500', 'pro_monthly'];
window.onAppMintProducts = function (detail) { // same payload as appmint:products
if (detail.requestId !== 'store-check') return;
var found = detail.products.map(function (p) { return p.productId; });
var missing = WANTED.filter(function (id) { return found.indexOf(id) === -1; });
if (missing.length) console.warn('Not returned by Google Play: ' + missing.join(', '));
document.getElementById('store').hidden = found.length === 0;
};
if (window.WebToApk && typeof window.WebToApk.getProducts === 'function') {
window.WebToApk.getProducts('store-check', JSON.stringify(WANTED));
}Catatan: Tampilkan price persis seperti yang diberikan Play dan jangan pernah menyimpannya. ID yang tidak dikenal Play (belum dibuat, tidak aktif, atau aplikasi tidak dipasang dari Play) sekadar tidak ada di products; biarkan item itu tersembunyi. requestId yang tampak numerik dikembalikan sebagai angka, jadi gunakan id teks atau bandingkan dengan String().
isOwned jembatan#
window.WebToApk.isOwned(productId: String): Boolean
Pemeriksaan kepemilikan instan di perangkat (produk sekali beli dimiliki ATAU langganan aktif).
Contoh
Memeriksa, seketika dan secara offline, apakah pengguna memiliki salah satu produk Play Anda (produk sekali beli, atau langganan yang masih aktif).
Mengembalikan: true / false, secara sinkron. Memerlukan: π° Jual Produk Dalam Aplikasi di wizard build. Dengan sakelar itu aktif ia juga menjawab untuk produk Hapus Iklan (IAP); di aplikasi yang hanya menjual penghapusan iklan (sakelar mati) ia menjawab false untuk setiap produk, jadi periksa penghapusan iklan dengan isPremium() di sana.
function hasPro() {
var b = window.WebToApk;
return !!(b && typeof b.isOwned === 'function' && b.isOwned('pro_unlock'));
}
function paint() {
document.getElementById('pro-features').hidden = !hasPro();
document.getElementById('buy-pro').hidden = hasPro();
}
paint();
// Ownership changes: a purchase landed, or the launch restore / a refund changed the list.
window.addEventListener('appmint:purchase', paint);
window.addEventListener('appmint:owned-changed', paint);Periksa saat digunakan: ekspor khusus Pro berjalan jika dimiliki, dan jika tidak membuka lembar pembelian lalu selesai begitu pembelian masuk:
var exportWaiting = false;
document.getElementById('export-hd').addEventListener('click', function () {
var b = window.WebToApk;
if (!b || typeof b.isOwned !== 'function') { exportStandard(); return; } // browser
if (b.isOwned('pro_unlock')) { exportHd(); return; }
exportWaiting = true;
b.purchase('pro_unlock');
});
window.addEventListener('appmint:purchase', function (e) {
if (e.detail.productId === 'pro_unlock' && exportWaiting) {
exportWaiting = false;
exportHd(); // finish what the user asked for
}
});Pro lewat salah satu dari beberapa paket: produk bulanan, tahunan, dan seumur hidup semuanya dihitung:
var PRO_PLANS = ['pro_monthly', 'pro_yearly', 'pro_lifetime'];
function isPro() {
var b = window.WebToApk;
if (!b || typeof b.isOwned !== 'function') return false;
return PRO_PLANS.some(function (id) { return b.isOwned(id); });
}
function paintPlan() { document.body.classList.toggle('pro', isPro()); }
paintPlan();
window.addEventListener('appmint:purchase', paintPlan);
window.addEventListener('appmint:owned-changed', paintPlan); // an expired subscription or a refundCatatan: Daftar yang dimiliki dipulihkan dari akun Google pengguna di setiap peluncuran dan disimpan di ponsel, sehingga bekerja offline dan setelah pasang ulang, tanpa server. Selalu false di browser. Langganan berhenti dihitung begitu Play tidak lagi melaporkannya sebagai aktif.
isPremium jembatan#
window.WebToApk.isPremium(): Boolean
Benar saat pengguna memiliki "Hapus Iklan" (hak akses Play Billing, dipulihkan otomatis di setiap peluncuran). Sesuai dengan alasan 'ad_free' di 'appmint:ad-unavailable'.
Contoh
Memberi tahu apakah pengguna memiliki produk "Hapus Iklan" Anda, sehingga halaman dapat menyembunyikan tombol "Hapus iklan" dan UI terkait iklan.
Mengembalikan: true / false, secara sinkron. Memerlukan: Iklan aktif dan Hapus Iklan (IAP) dengan ID Produk Play Store Anda. Tanpa itu nilainya selalu false.
var removeAdsBtn = document.getElementById('remove-ads');
function paintPremium(premium) {
removeAdsBtn.hidden = premium;
document.body.classList.toggle('premium', premium);
}
var b = window.WebToApk;
paintPremium(!!(b && typeof b.isPremium === 'function' && b.isPremium()));
// Changes later: purchase finished, restore at launch, refund or expired subscription.
window.addEventListener('appmint:premium', function (e) {
paintPremium(e.detail.premium); // e.detail = {premium: true | false}
});Perubahan yang sama juga memanggil hook halaman, jika Anda mendefinisikannya:
window.onAppMintPremiumChanged = function (info) { paintPremium(info.premium); }; // {premium}Tampilkan penawaran "Hapus iklan" Anda sendiri di layar game-over hanya kepada pemain yang masih melihat iklan:
function showGameOver(score) {
document.getElementById('final-score').textContent = score;
var b = window.WebToApk;
var premium = !!(b && typeof b.isPremium === 'function' && b.isPremium());
// Ad-free already, or a plain browser (no ads there): no offer.
document.getElementById('remove-ads-offer').hidden = premium || !b;
document.getElementById('game-over').hidden = false;
}Catatan: Kepemilikan dipulihkan dari akun Google pengguna di setiap peluncuran, jadi baca sekali saat mulai lalu ikuti appmint:premium. Kolom ID Produk menerima beberapa ID yang dipisahkan koma (misalnya paket bulanan dan seumur hidup); memiliki salah satunya sudah dihitung. Selama premium, iklan dihapus dan panggilan iklan menjawab ad_free.
purchase jembatan#
window.WebToApk.purchase(productId: String)
Membuka lembar pembelian Google untuk [productId] (produk sekali beli atau langganan - ditentukan otomatis). Berikan akses HANYA pada 'appmint:purchase' {productId, owned:true}; kegagalan memicu 'appmint:purchase-failed' {reason}. Yang sudah dimiliki memicu ulang event pembelian agar halaman dapat membuka dengan idempoten.
Contoh
Membuka lembar pembelian Google Play untuk salah satu produk Anda (produk sekali beli atau langganan; aplikasi mencari tahu yang mana).
Mengembalikan: tidak ada saat ini. Keberhasilan memicu appmint:purchase {productId, owned: true}. Kegagalan memicu appmint:purchase-failed {productId, reason}. Memerlukan: π° Jual Produk Dalam Aplikasi di wizard build, dan produk yang dibuat dan aktif di Play Console.
Buka HANYA di handler appmint:purchase, jangan pernah saat diketuk:
var b = window.WebToApk;
var buyBtn = document.getElementById('buy-pro');
if (!b || typeof b.purchase !== 'function') {
buyBtn.hidden = true; // browser: nothing to buy here
} else {
buyBtn.addEventListener('click', function () {
buyBtn.disabled = true;
b.purchase('pro_unlock');
});
}
window.addEventListener('appmint:purchase', function (e) {
if (e.detail.productId === 'pro_unlock') unlockPro(); // safe to run twice
buyBtn.disabled = false;
});
window.addEventListener('appmint:purchase-failed', function (e) {
buyBtn.disabled = false;
if (e.detail.reason === 'pending') showMessage('Payment pending. It unlocks by itself once Google confirms.');
else showMessage('Purchase is not available right now (' + e.detail.reason + ').');
});
// A cancel on Google's sheet sends no event: release the button when the page is visible again.
document.addEventListener('visibilitychange', function () { if (!document.hidden) buyBtn.disabled = false; });Hasil yang sama juga memanggil window.onAppMintPurchase(detail) dan window.onAppMintPurchaseFailed(detail) jika Anda mendefinisikannya.
Beli paket yang dipilih pengguna di pemilih langganan, dan sembunyikan paket yang ID-nya tidak dikenal Play:
var picked = 'pro_yearly';
document.querySelectorAll('.plan').forEach(function (card) {
card.addEventListener('click', function () { picked = card.getAttribute('data-product'); });
});
document.getElementById('subscribe').addEventListener('click', function () {
var b = window.WebToApk;
if (b && typeof b.purchase === 'function') b.purchase(picked);
});
window.addEventListener('appmint:purchase', function (e) {
if (/^pro_/.test(e.detail.productId)) unlockPro(); // any plan unlocks Pro
});
window.addEventListener('appmint:purchase-failed', function (e) {
if (e.detail.reason !== 'not_found') return;
var card = document.querySelector('.plan[data-product="' + e.detail.productId + '"]');
if (card) card.hidden = true;
});Buka paket level dari level terkunci yang diketuk pemain, lalu buka level itu:
var wantedLevel = null;
function openLevel(n) {
var b = window.WebToApk;
var owned = !!(b && typeof b.isOwned === 'function' && b.isOwned('level_pack_2'));
if (n <= 10 || owned) { startLevel(n); return; }
if (!b || typeof b.purchase !== 'function') { showMessage('Levels 11-20 are in the Android app.'); return; }
wantedLevel = n;
b.purchase('level_pack_2');
}
window.onAppMintPurchase = function (detail) { // {productId, owned: true}
if (detail.productId !== 'level_pack_2' || !wantedLevel) return;
var n = wantedLevel;
wantedLevel = null;
startLevel(n);
};Catatan: Alasan: disabled (Jual Produk Dalam Aplikasi mati), not_found (ID tidak ada di Play Console), billing_error, pending (pengguna membayar dengan metode lambat; pembukaan datang belakangan lewat pemulihan saat peluncuran dan appmint:owned-changed). Jika produk sudah dimiliki, appmint:purchase aktif lagi seketika, jadi pembukaan Anda harus aman diulang. Aplikasi juga menampilkan pesan pendeknya sendiri untuk kesalahan.
revenuecat jembatan#
window.WebToApk.revenuecat(action: String, argsJson: String, callbackId: String)
Satu [action] RevenueCat dengan [argsJson]; mengembalikan [callbackId] dengan {ok, β¦} atau {ok:false, error}.
Contoh
Metode mentah di balik AppMint.revenuecat - gunakan pembungkusnya. Satu titik masuk: action adalah configure, getOfferings, purchase, restore, getCustomerInfo, logIn, logOut atau getAppUserId.
Mengembalikan: tidak ada secara langsung; mengembalikan window.__appmintReplies[callbackId] dengan jawaban yang sama dengan pembungkus. Memerlukan: langkah Integrasi β RevenueCat dengan kunci goog_ publik Anda.
window.__appmintReplies = window.__appmintReplies || {};
window.__appmintReplies['rc1'] = (r) => console.log(r.ok ? r.customerInfo : r.error);
WebToApk.revenuecat('getCustomerInfo', '{}', 'rc1');Konfigurasi dengan id pengguna dan baca penawarannya:
window.__appmintReplies = window.__appmintReplies || {};
window.__appmintReplies['rc-cfg'] = (r) => {
if (!r.ok) return console.warn('RevenueCat:', r.error);
window.__appmintReplies['rc-off'] = (o) => console.log(o.ok ? o.current : o.error);
WebToApk.revenuecat('getOfferings', '{}', 'rc-off');
};
if (window.WebToApk && WebToApk.revenuecat) {
WebToApk.revenuecat('configure', JSON.stringify({ appUserId: 'u_42' }), 'rc-cfg');
}Beli dan tangani penolakan:
window.__appmintReplies = window.__appmintReplies || {};
window.__appmintReplies['rc-buy'] = (r) => {
if (r.ok) return unlock(r.customerInfo.entitlements.active);
if (r.error === 'foreign_origin') return showMessage('Purchases only work inside the app.');
if (r.cancelled) return;
showMessage('Purchase failed: ' + (r.message || r.error));
};
WebToApk.revenuecat('purchase', JSON.stringify({ package: '$rc_monthly' }), 'rc-buy');Catatan: Lihat AppMint.revenuecat untuk argumen dan jawaban setiap aksi.
startRemoveAdsPurchase jembatan#
window.WebToApk.startRemoveAdsPurchase()
Jalankan alur pembelian "Hapus Iklan" Google Play dari halaman (dimulai pengguna, misalnya tombol "Hapus iklan" milik aplikasi sendiri). Tanpa ini SATU-SATUNYA titik masuk adalah item menu samping native - tidak terjangkau di aplikasi yang dibuat tanpa menu samping. Hasilnya sampai ke halaman lewat 'appmint:premium' {premium:true}; yang sudah dimiliki memberi tahu lagi segera; kesalahan alur muncul sebagai toast native.
Contoh
Membuka lembar pembelian Google Play untuk produk "Hapus Iklan" Anda, dari tombol "Hapus iklan" milik halaman Anda sendiri.
Mengembalikan: tidak ada. Hasilnya datang sebagai appmint:premium {premium: true} (dan window.onAppMintPremiumChanged). Memerlukan: Iklan aktif dan Hapus Iklan (IAP) dengan ID Produk Play Store Anda (ID pertama yang tercantum yang dijual). Saat Menu Samping aktif, aplikasi juga punya item Hapus Iklan sendiri di sana (disembunyikan setelah pengguna memilikinya); dengan menu samping mati, tombol ini di halaman Anda adalah satu-satunya cara membeli.
var b = window.WebToApk;
var btn = document.getElementById('remove-ads');
if (!b || typeof b.startRemoveAdsPurchase !== 'function' || b.isPremium()) {
btn.hidden = true; // browser, or already ad-free
} else {
btn.addEventListener('click', function () {
btn.disabled = true;
b.startRemoveAdsPurchase();
});
}
window.addEventListener('appmint:premium', function (e) {
if (e.detail.premium) { btn.hidden = true; showMessage('Thank you! Ads are gone.'); }
});
// A cancel on Google's sheet sends no event: release the button when the page is visible again.
document.addEventListener('visibilitychange', function () {
if (!document.hidden) btn.disabled = false;
});Baris pengaturan yang menunjukkan apakah iklan dihapus, dengan tombol untuk membelinya:
var row = document.getElementById('ads-setting');
var btn = row.querySelector('button');
var b = window.WebToApk;
function paintAdsRow(premium) {
row.querySelector('.status').textContent = premium ? 'Ads removed. Thank you!' : 'Ads are on';
btn.hidden = premium;
}
if (b && typeof b.startRemoveAdsPurchase === 'function') {
paintAdsRow(b.isPremium());
btn.addEventListener('click', function () { b.startRemoveAdsPurchase(); });
window.onAppMintPremiumChanged = function (info) { paintAdsRow(info.premium); }; // {premium}
} else {
row.hidden = true; // browser: nothing to sell
}Tawarkan setelah setiap putaran ketiga selesai, dalam dialog yang dijawab pemain dengan satu ketukan:
var rounds = 0;
var offer = document.getElementById('remove-ads-offer'); // a <dialog>
function onRoundFinished() {
var b = window.WebToApk;
if (!b || typeof b.startRemoveAdsPurchase !== 'function' || b.isPremium()) return;
rounds += 1;
if (rounds % 3 === 0) offer.showModal();
}
offer.querySelector('.buy').addEventListener('click', function () {
offer.close();
window.WebToApk.startRemoveAdsPurchase(); // opened from the player's own tap
});
offer.querySelector('.later').addEventListener('click', function () { offer.close(); });Catatan: Jika pengguna sudah memilikinya, appmint:premium {premium:true} aktif seketika. Kesalahan (Play tidak terjangkau, produk tidak ditemukan di Play Console) ditampilkan aplikasi sebagai pesan native singkat; halaman tidak mendapat event untuknya. Uji dengan aplikasi yang dipasang dari jalur pengujian Play.