API du pont JavaScript
Achats et droits d'accès
Vendre depuis votre propre page et demander ce que cet acheteur possède déjà. Consultez le guide des achats intégrés pour le code qui entoure ces appels.
AppMint.revenuecat assistant#
AppMint.revenuecat — RevenueCat subscriptions
Exemple
Abonnements et achats uniques via votre projet RevenueCat : offres, achats, restaurations et droits d'accès. Les achats passent par Google Play dans l'application ; RevenueCat enregistre chacun avant que l'application ne le finalise.
Renvoie : chaque méthode renvoie une Promise qui se résout toujours en {ok: true, …} ou {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}. Nécessite : étape Intégration → RevenueCat avec votre clé Google Play PUBLIQUE (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();Associez les achats à votre propre compte :
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();
};
}Affichez un message honnête pour chaque échec :
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);
}Remarques : customerInfo = {appUserId, originalAppUserId, requestDate, managementUrl, entitlements: {active, all}, activeSubscriptions, allPurchasedProductIds, nonSubscriptionTransactions} ; chaque droit d'accès {identifier, isActive, productIdentifier, expirationDate, latestPurchaseDate, willRenew, periodType, store, isSandbox}. Passez {consumable: true} pour les pièces/crédits afin qu'ils puissent être rachetés ; les abonnements et les déblocages à achat unique sont confirmés. Hors ligne, getCustomerInfo répond avec les dernières informations connues et stale: true ; un achat effectué alors que RevenueCat est injoignable est tout de même finalisé et son reçu est envoyé au prochain configure/restore (receiptQueued: true). Erreurs : not_configured, network, revenuecat_error (avec status, code, message), billing_unavailable, billing_error, product_not_found, offer_not_found, cancelled, pending, already_anonymous, foreign_origin. Configurez la connexion du compte de service Google Play de RevenueCat comme RevenueCat le demande ; ne mettez jamais de clé secrète sk_ dans une application. Les achats AppMintIAP existants continuent de fonctionner sans changement.
AppMintIAP.available assistant#
window.AppMintIAP.available
Exemple
Une propriété (pas une fonction) : true lorsque cette page peut réellement acheter via Google Play.
Renvoie : une propriété booléenne. Nécessite : 💰 Vendre des produits intégrés. Sans cela, window.AppMintIAP n'existe tout simplement pas.
Vérifiez-le avant d'afficher quoi que ce soit à vendre, et dites clairement lorsque l'achat n'est pas possible ici :
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';
}Un seul bouton de mise à niveau pour votre site web et votre application : Google Play dans l'application, votre propre paiement sur le 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
}
});Dans React ou Next.js, lisez-le dans un effet (le rendu côté serveur n'a pas de window), afin que rien à acheter ne soit affiché en dehors de l'application :
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;Remarques : window.AppMintIAP est installé avant l'exécution du premier script de votre page ; ce n'est que sur les WebViews antérieures à 2021 environ qu'il arrive une fois la page entièrement chargée, donc le code qui doit aussi s'y exécuter doit l'attendre (voir le motif whenIap dans AppMintIAP.sell). La même valeur se trouve dans AppMintIAP.state().available. Envelopper l'offre dans data-iap-container la masque en dehors de l'application sans aucun code.
AppMintIAP.buy assistant#
window.AppMintIAP.buy(id)
Exemple
Ouvre la fenêtre d'achat de Google Play pour un produit, depuis votre propre bouton. Elle n'accorde rien par elle-même.
Renvoie : rien. Le résultat arrive sous la forme appmint:purchase {productId, owned:true} (et via AppMintIAP.onChange) ou appmint:purchase-failed {productId, reason}. Nécessite : 💰 Vendre des produits intégrés et le produit actif dans la Play Console.
Utilisez-le lorsque vous dessinez le bouton vous-même ; le déblocage vient de la possession, pas du toucher :
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 eventUn sélecteur de formule avec un seul bouton « Continuer » qui achète la formule sélectionnée :
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.');
});Appuyer sur un outil réservé à Pro l'exécute si le produit est possédé, sinon ouvre la fenêtre d'achat :
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');
});Remarques : buy() est le simple WebToApk.purchase(id) : pas de libellé d'occupation, pas de vérification de groupe. Les boutons reliés avec sell(), start() ou data-iap-buy font tout cela pour vous ; préférez-les donc.
AppMintIAP.isOwned assistant#
window.AppMintIAP.isOwned(id)
Exemple
Vérifie, instantanément et hors ligne, si l'utilisateur possède un produit (un produit à achat unique, ou un abonnement encore actif).
Renvoie : true / false, de façon synchrone ; false en dehors de l'application. Nécessite : 💰 Vendre des produits intégrés.
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 via l'une de plusieurs formules : un produit mensuel, un annuel et un à vie comptent tous :
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 refundUne garde de route : les écrans réservés à Pro renvoient un utilisateur sans Pro vers l'écran de mise à niveau :
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();Remarques : Même réponse que WebToApk.isOwned(id). La possession est restaurée depuis le compte Google de l'utilisateur à chaque lancement et enregistrée sur le téléphone ; elle survit donc aux réinstallations et fonctionne hors ligne. Ne conservez jamais le droit d'accès uniquement dans localStorage.
AppMintIAP.onChange assistant#
window.AppMintIAP.onChange(fn)
Exemple
Appelle votre fonction avec l'état des achats immédiatement, puis à chaque changement (arrivée des prix, achat effectué, restauration au lancement, remboursement, abonnement expiré).
Renvoie : une fonction de désabonnement. fn reçoit {available, adFree, owned, ownedIds, products} ; products vaut {id: {price, currency, type, period, owned}} pour les produits que l'assistant connaît (via sell, start ou data-iap-buy). Nécessite : 💰 Vendre des produits intégrés.
Un hook React. Il renvoie la fonction de désabonnement directement depuis l'effet :
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;Un paywall en JavaScript pur : suivez deux formules sans boutons, puis dessinez leurs prix et la formule actuelle à chaque changement :
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;
});
});
}Abonnez-vous pendant qu'un écran de compte est ouvert et désabonnez-vous à sa fermeture. adFree et ownedIds sont renseignés une fois l'assistant en marche (la page utilise sell(), start() ou 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');
}Remarques : Lisez la possession produit par produit (ownedIds ou products[id].owned). Le owned de niveau supérieur signifie seulement « quelque chose est possédé ». adFree est le droit d'accès Supprimer les publicités (WebToApk.isPremium()). Un produit n'est dans products qu'après la réponse de Google à son sujet.
AppMintIAP.owned assistant#
window.AppMintIAP.owned()
Exemple
Liste tous les ID de produit que l'utilisateur possède.
Renvoie : un tableau d'ID de produit, de façon synchrone, par exemple ['pro_unlock', 'level_pack_2'] ; [] en dehors de l'application ou lorsque rien n'est possédé. Nécessite : 💰 Vendre des produits intégrés.
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);Comptez combien de niveaux sont ouverts lorsque chaque pack de niveaux est son propre produit :
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);Une liste « Mes achats » sur votre écran de paramètres, avec votre propre nom pour chaque produit :
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);Remarques : Analysé pour vous à partir de WebToApk.getOwnedProducts(). Inclut les abonnements actifs et le produit Supprimer les publicités. Fonctionne hors ligne.
AppMintIAP.refresh assistant#
window.AppMintIAP.refresh()
Exemple
Recherche à nouveau les boutons data-iap-buy ajoutés depuis le dernier balayage et les relie (ou démarre l'assistant s'il n'en avait encore trouvé aucun).
Renvoie : l'objet AppMintIAP. Nécessite : 💰 Vendre des produits intégrés.
L'assistant surveille déjà la page à la recherche de nouveaux boutons data-iap-buy, vous en avez donc rarement besoin. Appelez-le après avoir inséré des boutons d'une manière que l'observateur pourrait manquer, par exemple en modifiant les attributs d'un élément existant :
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();
}Des cartes de boutique dont les produits viennent de votre propre serveur : les boutons sont déjà sur la page, seuls leurs attributs changent ; reliez-les donc avec 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();
});Un lot de fêtes mis en vente à une heure précise, sur un bouton présent sur la page dès le départ :
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 itRemarques : Chaque nouveau bouton reçoit son prix de Google et son état de possession. Un bouton déjà relié n'est pas relié deux fois. Les boutons dont l'élément a été retiré de la page sont abandonnés.
AppMintIAP.sell assistant#
window.AppMintIAP.sell(id, button, opts)
Exemple
Relie un produit à un bouton en une seule ligne. Appelez-la une fois par produit.
Renvoie : l'objet AppMintIAP (pour le chaînage). Nécessite : 💰 Vendre des produits intégrés dans l'assistant de compilation et le produit créé et actif dans la Play Console. window.AppMintIAP est installé avant l'exécution du premier script de votre page (sur les WebViews antérieures à 2021 environ, une fois la page entièrement chargée - le motif whenIap ci-dessous couvre les deux cas).
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}' });
});Plusieurs formules où l'achat de l'une remplace les autres partagent un group:
whenIap(function (iap) {
iap.sell('pass_monthly', '#monthly', { label: 'Monthly — {price}', group: 'pass' })
.sell('pass_yearly', '#yearly', { label: 'Yearly — {price}', group: 'pass' });
});Suivez un produit sans bouton et dessinez l'offre vous-même (null ne relie rien ; votre bouton appelle 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');
});
});Reliez un bouton qui n'existe qu'une fois la boîte de dialogue de votre boutique ouverte (un appel tardif rejoint l'assistant en cours) :
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
});
});Remarques : button est un élément ou un sélecteur CSS ; passez null pour suivre un produit sans bouton (votre propre interface lit AppMintIAP.state()). Autres opts : labelEl (l'élément à l'intérieur du bouton dont le texte change). Cela fonctionne avant ou après le démarrage de l'assistant, y compris depuis un effet React. Équivaut aux attributs data-iap-buy (voir AppMintIAP.start). Ne débloquez le contenu payant qu'à partir de la possession (isOwned, onChange), jamais dans votre propre gestionnaire de clic.
AppMintIAP.start assistant#
window.AppMintIAP.start(config) and the data-iap-* attributes
Exemple
Relie vos boutons d'achat à Google Play en une seule étape : prix à jour sur les boutons, fenêtre d'achat au toucher, libellés « possédé », blocs de remerciement, restauration au lancement et message visible pour chaque échec.
Renvoie : l'objet AppMintIAP (pour le chaînage). Nécessite : 💰 Vendre des produits intégrés dans l'assistant de compilation et les ID de produit créés et actifs dans la Play Console. L'application installe window.AppMintIAP avant l'exécution du premier script de votre page (sur les WebViews antérieures à 2021 environ, seulement une fois la page entièrement chargée).
Sans aucun code : balisez le HTML. L'assistant lit ces attributs de lui-même dès qu'il est injecté, et surveille les boutons que React ou Vue affichent plus tard :
<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 -->Avec du code : appelez start() vous-même lorsque vous voulez votre propre fonction de message ou vos propres hooks de déblocage. Utilisez-le sur les pages SANS boutons data-iap-buy dans le HTML (avec eux, l'assistant a déjà démarré et un second démarrage ne fait rien) :
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
});
});Dans React : démarrez avec des formules sans button (rien n'est dessiné) et laissez React faire le rendu à partir de onChange, qui renvoie la fonction de désabonnement pour l'effet :
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>;
}Remarques : Attributs : data-iap-buy="id", data-iap-label ({price} devient le prix à jour de Play), data-iap-owned (libellé une fois possédé ; par défaut « ✓ Acheté »), data-iap-group (formules qui se remplacent mutuellement ; une fois l'une possédée, les autres se masquent sauf si hideOtherPlans: false), data-iap-show / data-iap-hide (valeur = ID de produit ou groupe ; vide = tout ce qui est possédé), data-iap-text (la partie d'un bouton dont le texte change), data-iap-container. Un bouton possédé reçoit data-iap-state="owned" et la classe iap-unlocked. Une annulation dans la fenêtre de Google n'envoie aucun événement ; le bouton est libéré quand la page est de nouveau visible (ou après 30 s). Appeler start(options) après que l'assistant a déjà démarré seul à partir des attributs data-iap-buy FUSIONNE les options (formules, afficher/masquer, conteneur, onUnlock/onLock, toast, busyLabel, hideOtherPlans) dans l'ensemble en cours ; l'appeler deux fois est sans danger. Une page qui fournit son propre appmint-iap.js le conserve - la copie intégrée s'efface.
AppMintIAP.started assistant#
window.AppMintIAP.started
Exemple
Une propriété (pas une fonction) : true dès que l'assistant fonctionne, c'est-à-dire qu'il a relié au moins un produit à partir des boutons start(), sell() ou data-iap-buy.
Renvoie : une propriété booléenne. Nécessite : 💰 Vendre des produits intégrés.
Utilisez-le pour choisir entre start() (avec vos propres options) et sell() (qui fonctionne aussi sur un assistant déjà en marche) :
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);Soyez averti pendant le développement lorsque rien n'a été relié sur la page (une faute de frappe dans data-iap-buy, ou les boutons ne se sont jamais affichés) :
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);
});Distinguez un écran de boutique de « chargement en cours » et « achats indisponibles » :
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());Remarques : Un second start() ne le démarre jamais deux fois (React StrictMode exécute les effets deux fois) ; ses options sont fusionnées dans l'ensemble en cours. Il reste false tant qu'aucun produit n'est encore relié ; l'assistant continue alors de surveiller la page à la recherche de boutons data-iap-buy.
AppMintIAP.state assistant#
window.AppMintIAP.state()
Exemple
Renvoie une fois l'état actuel des achats, sans abonnement.
Renvoie : {available, adFree, owned, ownedIds, products}, de façon synchrone. ownedIds est un tableau d'ID de produit ; products vaut {id: {price, currency, type: 'inapp'|'subs', period, owned}} pour les produits sur lesquels l'assistant a interrogé Google. Nécessite : 💰 Vendre des produits intégrés.
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();Vérifiez la possession au moment où le joueur appuie sur un niveau verrouillé, sinon envoyez-le vers votre boutique :
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);
}Masquez vos propres invites « passer sans publicité » une fois le droit d'accès sans publicité de la compilation actif, et listez les prix que Google a envoyés jusqu'ici :
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);
});
}Remarques : C'est un instantané. Pour suivre les changements, utilisez AppMintIAP.onChange(fn), qui donne le même objet à chaque changement. owned vaut true dès que quoi que ce soit est possédé ; vérifiez un produit avec ownedIds ou AppMintIAP.isOwned(id).
getOwnedProducts pont#
window.WebToApk.getOwnedProducts(): String
Tableau JSON de tous les ID de produit possédés - pour verrouiller des écrans entiers au démarrage.
Exemple
Liste tous les produits Play que l'utilisateur possède, pour débloquer des écrans entiers au démarrage.
Renvoie : une chaîne JSON (tableau d'ID de produit), de façon synchrone, par exemple '["pro_unlock","level_pack_2"]'. Analysez-la avec JSON.parse. '[]' lorsque rien n'est possédé. Nécessite : 💰 Vendre des produits intégrés dans l'assistant de compilation.
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', ...]}
});Le même changement appelle aussi window.onAppMintOwnedChanged(detail) si vous la définissez.
Une liste « Mes achats » sur votre écran de paramètres, avec votre propre nom pour chaque produit :
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);Déterminez le meilleur palier que l'utilisateur possède lorsque chaque palier est son propre produit :
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()); });Remarques : Inclut les produits à achat unique et les abonnements actifs, ainsi que le produit Supprimer les publicités si l'utilisateur l'a acheté. Fonctionne hors ligne ; la liste est enregistrée sur le téléphone à chaque restauration au lancement et à chaque achat. appmint:owned-changed ne se déclenche que lorsque la liste a réellement changé ; il peut donc ne pas se déclencher du tout lors d'un lancement normal.
getProducts pont#
window.WebToApk.getProducts(requestId: String, productIdsJson: String)
Demande à Play les détails à jour des ID de produit donnés (tableau JSON de chaînes). Le résultat arrive sous la forme 'appmint:products' {requestId, products:[{productId, type, title, description, price, currency, period?, owned}]}. Les ID que Play ne connaît pas sont absents du résultat - la page masque ces éléments.
Exemple
Demande à Google Play les détails à jour de vos produits : titre, description et prix localisé.
Renvoie : rien pour l'instant. La réponse arrive sous la forme de l'événement appmint:products : {requestId, products: [{productId, type, title, description, price, currency, period?, owned}]}. type vaut 'inapp' ou 'subs' ; period est une période ISO-8601 telle que 'P1M' (abonnements uniquement). Nécessite : 💰 Vendre des produits intégrés dans l'assistant de compilation. Le second argument est une chaîne JSON : utilisez 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' : '');
});
});La réponse est aussi transmise à window.onAppMintProducts(detail) si vous la définissez.
Un sélecteur de formule d'abonnement, chaque carte portant le prix de Play et sa période de facturation :
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']));
}Pendant les tests, consignez les ID que Google Play n'a pas renvoyés, via le hook de page :
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));
}Remarques : Affichez price exactement comme Play le donne et ne le stockez jamais. Un ID que Play ne connaît pas (non créé, non actif, ou l'application n'a pas été installée depuis Play) est simplement absent de products ; gardez cet élément masqué. Un requestId qui ressemble à un nombre revient sous forme de nombre ; utilisez donc un identifiant texte ou comparez avec String().
isOwned pont#
window.WebToApk.isOwned(productId: String): Boolean
Vérification instantanée de possession sur l'appareil (produit à achat unique possédé OU abonnement actif).
Exemple
Vérifie, instantanément et hors ligne, si l'utilisateur possède l'un de vos produits Play (un produit à achat unique, ou un abonnement encore actif).
Renvoie : true / false, de façon synchrone. Nécessite : 💰 Vendre des produits intégrés dans l'assistant de compilation. Avec cet interrupteur activé, elle répond aussi pour le produit Supprimer les publicités (achat intégré) ; dans une application qui ne vend que la suppression des publicités (interrupteur désactivé), elle répond false pour tous les produits ; vérifiez donc la suppression des publicités avec isPremium() dans ce cas.
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);Vérifiez au moment de l'usage : une exportation réservée à Pro s'exécute si le produit est possédé, sinon ouvre la fenêtre d'achat et se termine une fois l'achat effectué :
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 via l'une de plusieurs formules : un produit mensuel, un annuel et un à vie comptent tous :
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 refundRemarques : La liste des produits possédés est restaurée depuis le compte Google de l'utilisateur à chaque lancement et enregistrée sur le téléphone, donc elle fonctionne hors ligne et après une réinstallation, sans serveur. Toujours false dans un navigateur. Un abonnement cesse d'être pris en compte dès que Play ne le signale plus comme actif.
isPremium pont#
window.WebToApk.isPremium(): Boolean
Vrai lorsque l'utilisateur possède « Supprimer les publicités » (droit d'accès Play Billing, restauré automatiquement à chaque lancement). Correspond à la raison 'ad_free' dans 'appmint:ad-unavailable'.
Exemple
Indique si l'utilisateur possède votre produit « Supprimer les publicités », afin que la page puisse masquer son bouton « Supprimer les publicités » et l'interface liée aux publicités.
Renvoie : true / false, de façon synchrone. Nécessite : Publicités activées et Supprimer les publicités (achat intégré) avec votre ID de produit Play Store. Sans cela, c'est toujours 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}
});Le même changement appelle aussi un hook de page, si vous le définissez :
window.onAppMintPremiumChanged = function (info) { paintPremium(info.premium); }; // {premium}Affichez votre propre offre « Supprimer les publicités » sur l'écran de fin de partie uniquement aux joueurs qui voient encore des publicités :
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;
}Remarques : La possession est restaurée depuis le compte Google de l'utilisateur à chaque lancement ; lisez-la donc une fois au démarrage, puis suivez appmint:premium. Le champ ID de produit accepte plusieurs ID séparés par des virgules (par exemple une formule mensuelle et une à vie) ; en posséder une suffit. En mode premium, les publicités sont supprimées et les appels publicitaires répondent ad_free.
purchase pont#
window.WebToApk.purchase(productId: String)
Ouvre la fenêtre d'achat de Google pour [productId] (produit à achat unique ou abonnement - déterminé automatiquement). N'accordez QUE sur 'appmint:purchase' {productId, owned:true} ; les échecs déclenchent 'appmint:purchase-failed' {reason}. Un produit déjà possédé redéclenche l'événement d'achat afin que la page puisse débloquer de façon idempotente.
Exemple
Ouvre la fenêtre d'achat de Google Play pour l'un de vos produits (un produit à achat unique ou un abonnement ; l'application détermine lequel).
Renvoie : rien pour l'instant. Un succès déclenche appmint:purchase {productId, owned: true}. Un échec déclenche appmint:purchase-failed {productId, reason}. Nécessite : 💰 Vendre des produits intégrés dans l'assistant de compilation, et le produit créé et actif dans la Play Console.
Débloquez UNIQUEMENT dans le gestionnaire appmint:purchase, jamais au toucher :
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; });Les mêmes résultats appellent aussi window.onAppMintPurchase(detail) et window.onAppMintPurchaseFailed(detail) si vous les définissez.
Achetez la formule choisie par l'utilisateur dans un sélecteur d'abonnement, et masquez une formule dont l'ID est inconnu de 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;
});Débloquez un pack de niveaux depuis le niveau verrouillé sur lequel le joueur a appuyé, puis ouvrez ce niveau :
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);
};Remarques : Raisons : disabled (Vendre des produits intégrés est désactivé), not_found (l'ID n'est pas dans la Play Console), billing_error, pending (l'utilisateur a payé avec un moyen lent ; le déblocage arrive plus tard via la restauration au lancement et appmint:owned-changed). Si le produit est déjà possédé, appmint:purchase se déclenche de nouveau immédiatement, votre déblocage doit donc pouvoir être répété sans risque. L'application affiche aussi son propre court message en cas d'erreur.
revenuecat pont#
window.WebToApk.revenuecat(action: String, argsJson: String, callbackId: String)
Une [action] RevenueCat avec [argsJson] ; résout [callbackId] avec {ok, …} ou {ok:false, error}.
Exemple
La méthode brute derrière AppMint.revenuecat - utilisez l'enveloppe. Un seul point d'entrée : action vaut configure, getOfferings, purchase, restore, getCustomerInfo, logIn, logOut ou getAppUserId.
Renvoie : rien directement ; résout window.__appmintReplies[callbackId] avec la même réponse que celle de l'enveloppe. Nécessite : étape Intégration → RevenueCat avec votre clé publique goog_.
window.__appmintReplies = window.__appmintReplies || {};
window.__appmintReplies['rc1'] = (r) => console.log(r.ok ? r.customerInfo : r.error);
WebToApk.revenuecat('getCustomerInfo', '{}', 'rc1');Configurez avec un identifiant utilisateur et lisez les offres :
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');
}Achetez et gérez les refus :
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');Remarques : Voir AppMint.revenuecat pour les arguments et les réponses de chaque action.
startRemoveAdsPurchase pont#
window.WebToApk.startRemoveAdsPurchase()
Lance depuis la page le flux d'achat Google Play « Supprimer les publicités » (à l'initiative de l'utilisateur, par exemple le bouton « Supprimer les publicités » de l'application). Sans cela, le SEUL point d'entrée était l'élément du menu latéral natif - inaccessible dans les applications compilées sans menu latéral. Le résultat parvient à la page via 'appmint:premium' {premium:true} ; si le produit est déjà possédé, une nouvelle notification est envoyée immédiatement ; les erreurs du flux apparaissent dans un toast natif.
Exemple
Ouvre la fenêtre d'achat de Google Play pour votre produit « Supprimer les publicités », depuis le bouton « Supprimer les publicités » de votre page.
Renvoie : rien. Le résultat arrive sous la forme appmint:premium {premium: true} (et window.onAppMintPremiumChanged). Nécessite : Publicités activées et Supprimer les publicités (achat intégré) avec votre ID de produit Play Store (le premier ID listé est celui qui est vendu). Lorsque le Menu latéral est activé, l'application possède aussi son propre élément Supprimer les publicités (masqué dès que l'utilisateur le possède) ; sans menu latéral, ce bouton sur votre page est le seul moyen d'acheter.
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;
});Une ligne de paramètres qui indique si les publicités sont supprimées, avec le bouton qui permet de l'acheter :
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
}Proposez-le après chaque troisième manche terminée, dans une boîte de dialogue à laquelle le joueur répond d'un toucher :
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(); });Remarques : Si l'utilisateur le possède déjà, appmint:premium {premium:true} se déclenche immédiatement. Les erreurs (Play injoignable, produit introuvable dans la Play Console) sont affichées par l'application sous forme de court message natif ; la page ne reçoit aucun événement à leur sujet. Testez avec une application installée depuis une piste de test Play.