JavaScript ब्रिज API
खरीदारी और अधिकार
अपने पेज से बिक्री करना, और यह पूछना कि इस खरीदार के पास पहले से क्या है। इनके आसपास का कोड देखने के लिए इन-ऐप खरीदारी गाइड देखें।
AppMint.revenuecat हेल्पर#
AppMint.revenuecat — RevenueCat subscriptions
उदाहरण
आपके RevenueCat प्रोजेक्ट के ज़रिए सब्सक्रिप्शन और एक बार की खरीदारी: ऑफ़रिंग, खरीदारी, रीस्टोर और अधिकार। ऐप में खरीदारी Google Play से होती है; ऐप के उसे पूरा करने से पहले RevenueCat हर खरीदारी दर्ज कर लेता है।
लौटाता है: हर मेथड एक प्रॉमिस लौटाता है जो हमेशा {ok: true, …} या {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}। ज़रूरत: Integrate चरण → RevenueCat, आपकी PUBLIC Google Play कुंजी के साथ (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();खरीदारी को अपने अकाउंट से जोड़ें:
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();
};
}हर नाकामी के लिए सच्चा संदेश दिखाएँ:
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);
}नोट: customerInfo = {appUserId, originalAppUserId, requestDate, managementUrl, entitlements: {active, all}, activeSubscriptions, allPurchasedProductIds, nonSubscriptionTransactions}; हर अधिकार {identifier, isActive, productIdentifier, expirationDate, latestPurchaseDate, willRenew, periodType, store, isSandbox}। सिक्कों/क्रेडिट के लिए {consumable: true} दें ताकि उन्हें दोबारा खरीदा जा सके; सब्सक्रिप्शन और एक बार के अनलॉक की पुष्टि की जाती है। ऑफ़लाइन होने पर, getCustomerInfo आखिरी ज्ञात जानकारी stale: true के साथ देता है; RevenueCat तक न पहुँच पाने के दौरान की गई खरीद फिर भी पूरी होती है और उसकी रसीद अगले configure/restore पर भेजी जाती है (receiptQueued: true)। त्रुटियाँ: not_configured, network, revenuecat_error (status, code, message के साथ), billing_unavailable, billing_error, product_not_found, offer_not_found, cancelled, pending, already_anonymous, foreign_origin। RevenueCat की Google Play सर्विस-अकाउंट कनेक्शन वैसे ही सेट करें जैसा RevenueCat कहे; किसी ऐप में गुप्त sk_ कुंजी कभी न डालें। मौजूदा AppMintIAP खरीदारियाँ बिना बदलाव के चलती रहती हैं।
AppMintIAP.available हेल्पर#
window.AppMintIAP.available
उदाहरण
एक प्रॉपर्टी (फ़ंक्शन नहीं): जब यह पेज सच में Google Play से खरीदारी कर सकता है तब true।
लौटाता है: एक बूलियन प्रॉपर्टी। ज़रूरत: 💰 Sell In-App Products। इसके बिना window.AppMintIAP बिल्कुल मौजूद ही नहीं होता।
बिक्री के लिए कुछ भी दिखाने से पहले इसे जाँचें, और साफ़ कहें कि यहाँ खरीदना संभव नहीं है:
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';
}आपकी वेबसाइट और ऐप के लिए एक अपग्रेड बटन: ऐप के अंदर Google Play, वेब पर आपका अपना चेकआउट:
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
}
});React या Next.js में इसे effect के अंदर पढ़ें (सर्वर रेंडर में window नहीं होता), ताकि ऐप के बाहर खरीदने के लिए कुछ भी न दिखे:
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;नोट: window.AppMintIAP आपके पेज की पहली स्क्रिप्ट चलने से पहले इंस्टॉल हो जाता है; सिर्फ़ लगभग 2021 से पुराने WebView पर यह पेज लोड पूरा होने के बाद आता है, इसलिए जिस कोड को वहाँ भी चलना है उसे इसका इंतज़ार करना चाहिए (AppMintIAP.sell में whenIap पैटर्न देखें)। यही मान AppMintIAP.state().available में है। ऑफ़र को data-iap-container में लपेटने से वह कोड के बिना ऐप के बाहर छिप जाता है।
AppMintIAP.buy हेल्पर#
window.AppMintIAP.buy(id)
उदाहरण
आपके अपने बटन से, किसी प्रोडक्ट के लिए Google Play की खरीदारी शीट खोलता है। यह अपने आप कुछ अनलॉक नहीं करता।
लौटाता है: कुछ नहीं। नतीजा appmint:purchase {productId, owned:true} के रूप में आता है (और AppMintIAP.onChange के ज़रिए) या appmint:purchase-failed {productId, reason} के रूप में। ज़रूरत: 💰 Sell In-App Products और Play Console में प्रोडक्ट का चालू होना।
इसे तब इस्तेमाल करें जब बटन आप खुद बनाते हैं; अनलॉक टैप से नहीं, बल्कि ख़रीद से आता है:
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 eventएक प्लान पिकर, जिसमें एक "Continue" बटन चुने हुए प्लान को खरीद लेता है:
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.');
});सिर्फ़-Pro टूल पर टैप करने से, खरीदा हुआ हो तो वह चलता है, वरना खरीदारी शीट खुलती है:
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');
});नोट: buy() सादा WebToApk.purchase(id) है: कोई व्यस्त लेबल नहीं, कोई ग्रुप जाँच नहीं। sell(), start() या data-iap-buy से जुड़े बटन यह सब आपके लिए करते हैं, इसलिए उन्हें प्राथमिकता दें।
AppMintIAP.isOwned हेल्पर#
window.AppMintIAP.isOwned(id)
उदाहरण
तुरंत और ऑफ़लाइन जाँचता है कि उपयोगकर्ता के पास कोई प्रोडक्ट है या नहीं (एक बार की खरीद, या अब भी चालू सब्सक्रिप्शन)।
लौटाता है: true / false, सिंक्रोनस रूप से; ऐप के बाहर false। ज़रूरत: 💰 Sell In-App Products.
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 offकई प्लान में से किसी से भी Pro: मासिक, वार्षिक और लाइफ़टाइम, तीनों प्रोडक्ट गिने जाते हैं:
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 refundरूट गार्ड: सिर्फ़-Pro स्क्रीन बिना Pro वाले यूज़र को अपग्रेड स्क्रीन पर भेज देती हैं:
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();ध्यान दें: जवाब वही है जो WebToApk.isOwned(id) का है। हर लॉन्च पर यूज़र के Google अकाउंट से ख़रीद वापस मिल जाती है और फ़ोन पर सेव हो जाती है, इसलिए यह दोबारा इंस्टॉल करने पर भी बनी रहती है और ऑफ़लाइन भी काम करती है। अधिकार को कभी सिर्फ़ यहाँ न रखें localStorage.
AppMintIAP.onChange हेल्पर#
window.AppMintIAP.onChange(fn)
उदाहरण
आपके फ़ंक्शन को तुरंत खरीदारी की स्थिति के साथ कॉल करता है, और हर बदलाव पर फिर से (कीमतें आईं, खरीदारी हुई, लॉन्च रीस्टोर, रिफ़ंड, सब्सक्रिप्शन ख़त्म)।
लौटाता है: अनसब्सक्राइब फ़ंक्शन। fn को {available, adFree, owned, ownedIds, products} मिलता है; products उन प्रोडक्ट के लिए {id: {price, currency, type, period, owned}} है जिन्हें हेल्पर जानता है (sell, start या data-iap-buy से)। ज़रूरत: 💰 Sell In-App Products.
एक React हुक। यह अनसब्सक्राइब फ़ंक्शन सीधे इफ़ेक्ट से लौटाता है:
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;सादे JavaScript वाला पेवॉल: बिना बटन के दो प्लान ट्रैक करें, फिर हर बदलाव पर उनकी कीमतें और मौजूदा प्लान बनाएँ:
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;
});
});
}अकाउंट स्क्रीन खुली होने पर सब्सक्राइब करें और बंद होने पर अनसब्सक्राइब करें। हेल्पर के चलने के बाद adFree और ownedIds भर जाते हैं (पेज sell(), start() या इस्तेमाल करता है 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');
}नोट: मालिकाना हक हर प्रोडक्ट के हिसाब से पढ़ें (ownedIds या products[id].owned)। ऊपरी स्तर का owned सिर्फ़ यह बताता है कि "कुछ भी खरीदा हुआ है"। adFree Remove Ads का अधिकार है (WebToApk.isPremium())। कोई प्रोडक्ट products में तभी आता है जब Google ने उसके बारे में जवाब दिया हो।
AppMintIAP.owned हेल्पर#
window.AppMintIAP.owned()
उदाहरण
यूज़र के पास मौजूद हर प्रोडक्ट ID की सूची देता है।
लौटाता है: प्रोडक्ट ID की array, तुरंत, जैसे ['pro_unlock', 'level_pack_2']; ऐप के बाहर या कुछ भी खरीदा न हो तो []। ज़रूरत: 💰 Sell In-App Products.
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);गिनें कि कितने लेवल खुले हैं जब हर लेवल पैक अपना अलग प्रोडक्ट हो:
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);आपकी सेटिंग्स स्क्रीन पर "My purchases" सूची, हर प्रोडक्ट के आपके अपने नाम के साथ:
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);ध्यान दें: इसे आपके लिए WebToApk.getOwnedProducts() से पार्स किया जाता है। इसमें चालू सब्सक्रिप्शन और Remove Ads प्रोडक्ट शामिल हैं। ऑफ़लाइन भी काम करता है।
AppMintIAP.refresh हेल्पर#
window.AppMintIAP.refresh()
उदाहरण
पिछले स्कैन के बाद जुड़े data-iap-buy बटनों को फिर से खोजता है और उन्हें जोड़ता है (या अगर अब तक कोई नहीं मिला था तो हेल्पर शुरू करता है)।
लौटाता है: AppMintIAP ऑब्जेक्ट। ज़रूरत: 💰 Sell In-App Products.
हेल्पर पहले से नए data-iap-buy बटन के लिए पेज पर नज़र रखता है, इसलिए आपको इसकी कम ही ज़रूरत पड़ती है। इसे तब कॉल करें जब आपने बटन ऐसे तरीके से जोड़े हों जो वॉचर से छूट सकता है, जैसे किसी मौजूदा एलिमेंट के एट्रिब्यूट बदलकर:
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();
}ऐसे शॉप कार्ड जिनके प्रोडक्ट आपके अपने सर्वर से आते हैं: बटन पेज पर पहले से हैं, सिर्फ़ उनके एट्रिब्यूट बदलते हैं, इसलिए उन्हें जोड़ें 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();
});एक हॉलिडे बंडल जो तय समय पर सेल पर आता है, ऐसे बटन पर जो शुरू से पेज पर था:
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 itध्यान दें: हर नए बटन को Google से उसकी कीमत और उसकी खरीदी हुई स्थिति मिलती है। जो बटन पहले से जुड़ा है, वह दो बार नहीं जुड़ता। जिन बटनों का एलिमेंट पेज से हटा दिया गया है, उन्हें छोड़ दिया जाता है।
AppMintIAP.sell हेल्पर#
window.AppMintIAP.sell(id, button, opts)
उदाहरण
एक लाइन में एक प्रोडक्ट को एक बटन से जोड़ता है। हर प्रोडक्ट के लिए एक बार कॉल करें।
लौटाता है: AppMintIAP ऑब्जेक्ट (चेनिंग के लिए)। ज़रूरत: बिल्ड विज़ार्ड में 💰 Sell In-App Products और Play Console में बना व चालू प्रोडक्ट। window.AppMintIAP आपके पेज की पहली स्क्रिप्ट चलने से पहले इंस्टॉल हो जाता है (लगभग 2021 से पुराने WebView पर, पेज लोड पूरा होने के बाद - नीचे का whenIap पैटर्न दोनों को संभालता है)।
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}' });
});कई प्लान जिनमें एक खरीदने पर बाकी बदल जाते हैं, एक साझा group:
whenIap(function (iap) {
iap.sell('pass_monthly', '#monthly', { label: 'Monthly — {price}', group: 'pass' })
.sell('pass_yearly', '#yearly', { label: 'Yearly — {price}', group: 'pass' });
});बिना बटन के प्रोडक्ट को ट्रैक करें और ऑफ़र खुद बनाएँ (null कुछ भी नहीं जोड़ता; आपका बटन कॉल करता है 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');
});
});ऐसा बटन जोड़ें जो आपका शॉप डायलॉग खुलने के बाद ही मौजूद होता है (देर से की गई कॉल चल रहे हेल्पर में जुड़ जाती है):
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
});
});ध्यान दें: button एक एलिमेंट या CSS सेलेक्टर है; बिना बटन के प्रोडक्ट को ट्रैक करने के लिए null दें (आपका अपना UI AppMintIAP.state() पढ़ता है)। अन्य opts: labelEl (बटन के अंदर का वह एलिमेंट जिसका टेक्स्ट बदलता है)। यह हेल्पर के शुरू होने से पहले या बाद में, यहाँ तक कि React effect से भी काम करता है। यह data-iap-buy एट्रिब्यूट जैसा ही है (देखें AppMintIAP.start)। पेड कंटेंट सिर्फ़ ख़रीद के आधार पर अनलॉक करें (isOwned, onChange), अपने क्लिक हैंडलर में कभी नहीं।
AppMintIAP.start हेल्पर#
window.AppMintIAP.start(config) and the data-iap-* attributes
उदाहरण
आपके खरीदें बटन को एक ही कदम में Google Play से जोड़ता है: बटन पर लाइव कीमतें, टैप पर खरीदारी शीट, "owned" लेबल, धन्यवाद ब्लॉक, लॉन्च पर रीस्टोर, और हर नाकामी के लिए दिखने वाला संदेश।
लौटाता है: AppMintIAP ऑब्जेक्ट (चेनिंग के लिए)। ज़रूरत: बिल्ड विज़ार्ड में 💰 Sell In-App Products और Play Console में बने व चालू प्रोडक्ट IDs। ऐप window.AppMintIAP को आपके पेज की पहली स्क्रिप्ट चलने से पहले इंस्टॉल करता है (लगभग 2021 से पुराने WebView पर, सिर्फ़ पेज लोड पूरा होने के बाद)।
बिल्कुल कोड नहीं: HTML पर निशान लगाएँ। हेल्पर इंजेक्ट होते ही ये एट्रिब्यूट खुद पढ़ लेता है, और React या Vue के बाद में बनाए बटन पर नज़र रखता है:
<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 -->कोड से: जब आप अपना संदेश फ़ंक्शन या अनलॉक हुक चाहते हों, तब start() खुद कॉल करें। इसे उन पेजों पर इस्तेमाल करें जिनके HTML में data-iap-buy बटन नहीं हैं (बटन होने पर हेल्पर पहले ही शुरू हो चुका होता है और दूसरा start कुछ नहीं करता):
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
});
});React में: ऐसे प्लान से शुरू करें जिनमें button न हो (कुछ नहीं बनता) और React को onChange से रेंडर करने दें, जो effect के लिए अनसब्सक्राइब फ़ंक्शन लौटाता है:
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>;
}ध्यान दें: एट्रिब्यूट: data-iap-buy="id", data-iap-label ({price} की जगह Play की लाइव कीमत आ जाती है), data-iap-owned (खरीदने के बाद का लेबल; डिफ़ॉल्ट "✓ Purchased"), data-iap-group (एक-दूसरे की जगह लेने वाले प्लान; एक के खरीदे जाने पर बाकी छिप जाते हैं, जब तक hideOtherPlans: false न हो), data-iap-show / data-iap-hide (मान = प्रोडक्ट id या ग्रुप; खाली = जो भी खरीदा हो), data-iap-text (बटन का वह हिस्सा जिसका टेक्स्ट बदलता है), data-iap-container। खरीदे हुए बटन को data-iap-state="owned" और क्लास iap-unlocked मिलती है। Google की शीट पर रद्द करने से कोई इवेंट नहीं आता; पेज दोबारा दिखने पर (या 30 सेकंड बाद) बटन फिर से चालू हो जाता है। जब हेल्पर data-iap-buy एट्रिब्यूट से अपने आप शुरू हो चुका हो, तब start(options) को कॉल करने पर विकल्प (प्लान, दिखाना/छिपाना, कंटेनर, onUnlock/onLock, toast, busyLabel, hideOtherPlans) चल रहे सेट में मिल जाते हैं; इसे दो बार कॉल करना नुकसानदेह नहीं है। जिस पेज के साथ अपना appmint-iap.js आता है, वह उसे रखता है - बिल्ट-इन कॉपी पीछे हट जाती है।
AppMintIAP.started हेल्पर#
window.AppMintIAP.started
उदाहरण
एक प्रॉपर्टी (फ़ंक्शन नहीं): हेल्पर चालू होने पर true, यानी उसने start(), sell() या data-iap-buy बटन से कम से कम एक प्रोडक्ट जोड़ा हो।
लौटाता है: एक बूलियन प्रॉपर्टी। ज़रूरत: 💰 Sell In-App Products.
यह तय करने के लिए इस्तेमाल करें कि start() (आपके अपने विकल्पों के साथ) चुनें या sell() (जो चल रहे हेल्पर पर भी काम करता है):
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);डेवलपमेंट के दौरान खुद को चेतावनी दें जब पेज पर कुछ भी जुड़ा न हो (data-iap-buy में टाइपो, या बटन कभी बने ही नहीं):
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);
});स्टोर स्क्रीन को "अभी लोड हो रहा है" और "खरीदारी उपलब्ध नहीं" से अलग पहचानें:
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());नोट: दूसरा start() इसे कभी दो बार शुरू नहीं करता (React StrictMode इफ़ेक्ट दो बार चलाता है); उसके विकल्प चालू सेट में मिला दिए जाते हैं। जब तक कोई प्रोडक्ट जुड़ा नहीं होता यह false रहता है; तब हेल्पर data-iap-buy बटन के लिए पेज पर नज़र रखता है।
AppMintIAP.state हेल्पर#
window.AppMintIAP.state()
उदाहरण
सब्सक्राइब किए बिना, मौजूदा खरीदारी की स्थिति एक बार लौटाता है।
लौटाता है: {available, adFree, owned, ownedIds, products}, तुरंत। ownedIds प्रोडक्ट ID की array है; products उन प्रोडक्ट के लिए {id: {price, currency, type: 'inapp'|'subs', period, owned}} है जिनके बारे में हेल्पर ने Google से पूछा है। ज़रूरत: 💰 Sell In-App Products.
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();जिस पल खिलाड़ी लॉक लेवल पर टैप करे, मालिकाना हक जाँचें, वरना उसे अपनी दुकान पर भेजें:
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);
}बिल्ड का विज्ञापन-मुक्त अधिकार चालू होते ही अपने "go ad-free" प्रॉम्प्ट छिपा दें, और 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);
});
}ध्यान दें: यह एक स्नैपशॉट है। बदलाव देखने के लिए AppMintIAP.onChange(fn) इस्तेमाल करें, जो हर बदलाव पर वही ऑब्जेक्ट देता है। कुछ भी खरीदा हुआ हो तो owned का मान true होता है; किसी प्रोडक्ट की जाँच ownedIds से करें या AppMintIAP.isOwned(id).
getOwnedProducts ब्रिज#
window.WebToApk.getOwnedProducts(): String
खरीदे हुए हर प्रोडक्ट ID की JSON array - शुरू में पूरी स्क्रीन को गेट करने के लिए।
उदाहरण
उपयोगकर्ता के पास जितने भी Play प्रोडक्ट हैं उन सबकी सूची, शुरू में पूरी स्क्रीन अनलॉक करने के लिए।
लौटाता है: एक JSON स्ट्रिंग (प्रोडक्ट ID की array), तुरंत, जैसे '["pro_unlock","level_pack_2"]'। इसे JSON.parse से पार्स करें। कुछ भी खरीदा न हो तो '[]'। ज़रूरत: बिल्ड विज़ार्ड में 💰 Sell In-App Products।
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', ...]}
});अगर आपने window.onAppMintOwnedChanged(detail) बनाया हो, तो यही बदलाव उसे भी कॉल करता है।
आपकी सेटिंग्स स्क्रीन पर "My purchases" सूची, हर प्रोडक्ट के आपके अपने नाम के साथ:
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);जब हर टियर अपना अलग प्रोडक्ट हो, तब यूज़र के पास सबसे ऊँचा कौन-सा टियर है, यह पता करें:
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()); });ध्यान दें: इसमें एक बार के प्रोडक्ट और चालू सब्सक्रिप्शन शामिल हैं, और अगर यूज़र ने Remove Ads प्रोडक्ट खरीदा हो तो वह भी। ऑफ़लाइन काम करता है; सूची हर लॉन्च रीस्टोर और खरीदारी पर फ़ोन में सेव होती है। appmint:owned-changed तभी चलता है जब सूची सच में बदली हो, इसलिए सामान्य लॉन्च पर हो सकता है यह बिल्कुल न चले।
getProducts ब्रिज#
window.WebToApk.getProducts(requestId: String, productIdsJson: String)
दिए गए प्रोडक्ट IDs (स्ट्रिंग की JSON ऐरे) की लाइव जानकारी Play से माँगता है। नतीजा 'appmint:products' {requestId, products:[{productId, type, title, description, price, currency, period?, owned}]} के रूप में आता है। जिन IDs को Play नहीं जानता वे नतीजे में नहीं होतीं - पेज उन आइटम को छिपा देता है।
उदाहरण
Google Play से आपके प्रोडक्ट की लाइव जानकारी माँगता है: शीर्षक, विवरण और स्थानीय कीमत।
लौटाता है: अभी कुछ नहीं। जवाब appmint:products इवेंट के रूप में आता है: {requestId, products: [{productId, type, title, description, price, currency, period?, owned}]}। type का मान 'inapp' या 'subs' होता है; period एक ISO-8601 अवधि है, जैसे 'P1M' (सिर्फ़ सब्सक्रिप्शन के लिए)। ज़रूरत: बिल्ड विज़ार्ड में 💰 Sell In-App Products। दूसरा आर्गुमेंट एक JSON स्ट्रिंग है: इस्तेमाल करें 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' : '');
});
});अगर आपने window.onAppMintProducts(detail) बनाया है, तो जवाब उसे भी दिया जाता है।
सब्सक्रिप्शन प्लान चुनने वाला, हर कार्ड पर Play की कीमत और उसकी बिलिंग अवधि के साथ:
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']));
}टेस्टिंग के दौरान, पेज हुक के ज़रिए वे IDs लॉग करें जो Google Play ने नहीं लौटाईं:
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));
}ध्यान दें: price को ठीक वैसा ही दिखाएँ जैसा Play देता है और उसे कभी सेव न करें। जिस ID को Play नहीं जानता (बनाई नहीं गई, चालू नहीं है, या ऐप Play से इंस्टॉल नहीं हुआ) वह products में बस नहीं आती; उस आइटम को छिपा रहने दें। संख्या जैसा दिखने वाला requestId संख्या के रूप में लौटता है, इसलिए टेक्स्ट वाली id इस्तेमाल करें या इससे तुलना करें String().
isOwned ब्रिज#
window.WebToApk.isOwned(productId: String): Boolean
डिवाइस पर तुरंत मालिकाना हक की जाँच (एक बार का प्रोडक्ट मौजूद हो या सब्सक्रिप्शन चालू हो)।
उदाहरण
तुरंत और ऑफ़लाइन जाँचता है कि क्या यूज़र के पास आपका कोई Play प्रोडक्ट है (एक बार का प्रोडक्ट, या अब भी चालू सब्सक्रिप्शन)।
लौटाता है: true / false, तुरंत। ज़रूरत: बिल्ड विज़ार्ड में 💰 Sell In-App Products। वह स्विच चालू होने पर यह Remove Ads (IAP) प्रोडक्ट के लिए भी जवाब देता है; जिस ऐप में सिर्फ़ विज्ञापन हटाना बिकता है (स्विच बंद), वह हर प्रोडक्ट के लिए false देता है, इसलिए वहाँ विज्ञापन हटाने की जाँच isPremium() से करें।
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);इस्तेमाल के समय जाँचें: सिर्फ़-Pro एक्सपोर्ट खरीदा हुआ हो तो चलता है, वरना खरीदारी शीट खुलती है और खरीदारी पूरी होने पर ख़त्म होता है:
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: मासिक, वार्षिक और लाइफ़टाइम, तीनों प्रोडक्ट गिने जाते हैं:
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 refundनोट: मालिकाना हक की सूची हर लॉन्च पर उपयोगकर्ता के Google अकाउंट से बहाल होती है और फ़ोन पर सेव रहती है, इसलिए यह ऑफ़लाइन और री-इंस्टॉल के बाद भी बिना सर्वर के काम करती है। ब्राउज़र में हमेशा false। जब Play किसी सब्सक्रिप्शन को चालू नहीं बताता तो वह गिना जाना बंद हो जाता है।
isPremium ब्रिज#
window.WebToApk.isPremium(): Boolean
यूज़र के पास "Remove Ads" होने पर True (Play Billing अधिकार, हर लॉन्च पर अपने आप वापस मिलता है)। इसमें कारण 'ad_free' से मेल खाता है 'appmint:ad-unavailable'.
उदाहरण
बताता है कि उपयोगकर्ता के पास आपका "Remove Ads" प्रोडक्ट है या नहीं, ताकि पेज अपना "Remove ads" बटन और विज्ञापन से जुड़ा UI छिपा सके।
लौटाता है: true / false, सिंक्रोनस रूप से। ज़रूरत: विज्ञापन चालू हों और Remove Ads (IAP) आपकी Play Store Product ID के साथ हो। इसके बिना यह हमेशा 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}
});यही बदलाव पेज के एक हुक को भी कॉल करता है, अगर आपने उसे बनाया हो:
window.onAppMintPremiumChanged = function (info) { paintPremium(info.premium); }; // {premium}गेम-ओवर स्क्रीन पर अपना "Remove ads" ऑफ़र सिर्फ़ उन खिलाड़ियों को दिखाएँ जिन्हें अब भी विज्ञापन दिखते हैं:
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;
}ध्यान दें: हर लॉन्च पर यूज़र के Google अकाउंट से ख़रीद वापस मिल जाती है, इसलिए इसे शुरू में एक बार पढ़ें और फिर appmint:premium को फ़ॉलो करें। Product ID फ़ील्ड में कॉमा से अलग की हुई कई ID डाली जा सकती हैं (जैसे एक मासिक प्लान और एक लाइफ़टाइम); इनमें से किसी का भी मालिक होना गिना जाता है। प्रीमियम रहने पर विज्ञापन हट जाते हैं और विज्ञापन कॉल जवाब देती हैं ad_free.
purchase ब्रिज#
window.WebToApk.purchase(productId: String)
[productId] के लिए Google की खरीदारी शीट खोलता है (एक बार का प्रोडक्ट या सब्सक्रिप्शन - अपने आप पहचान लिया जाता है)। अधिकार सिर्फ़ 'appmint:purchase' {productId, owned:true} पर दें; विफलता 'appmint:purchase-failed' {reason} चलाती है। पहले से खरीदा हुआ हो तो खरीदारी का इवेंट फिर चलता है, ताकि पेज बार-बार अनलॉक करने पर भी सुरक्षित रहे।
उदाहरण
आपके किसी प्रोडक्ट के लिए Google Play की खरीदारी शीट खोलता है (एक बार का प्रोडक्ट या सब्सक्रिप्शन; ऐप खुद पता लगा लेता है कि कौन-सा है)।
लौटाता है: अभी कुछ नहीं। सफल होने पर appmint:purchase {productId, owned: true} चलता है। विफल होने पर appmint:purchase-failed {productId, reason} चलता है। ज़रूरत: बिल्ड विज़ार्ड में 💰 Sell In-App Products, और Play Console में प्रोडक्ट बना हुआ और चालू होना।
अनलॉक सिर्फ़ appmint:purchase हैंडलर में करें, टैप पर कभी नहीं:
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; });अगर आपने window.onAppMintPurchase(detail) और window.onAppMintPurchaseFailed(detail) बनाए हों, तो यही नतीजे उन्हें भी कॉल करते हैं।
सब्सक्रिप्शन पिकर में यूज़र का चुना हुआ प्लान खरीदें, और जिस प्लान की ID 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;
});खिलाड़ी ने जिस लॉक्ड लेवल पर टैप किया, उससे लेवल पैक अनलॉक करें, फिर वह लेवल खोलें:
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);
};ध्यान दें: कारण: disabled (Sell In-App Products बंद है), not_found (यह ID Play Console में नहीं है), billing_error, pending (यूज़र ने धीमे तरीके से भुगतान किया; अनलॉक बाद में लॉन्च रीस्टोर और appmint:owned-changed के ज़रिए आता है)। अगर प्रोडक्ट पहले से खरीदा हुआ है, तो appmint:purchase तुरंत फिर से चलता है, इसलिए आपका अनलॉक बार-बार चलने पर भी सुरक्षित होना चाहिए। त्रुटियों के लिए ऐप अपना छोटा संदेश भी दिखाता है।
revenuecat ब्रिज#
window.WebToApk.revenuecat(action: String, argsJson: String, callbackId: String)
[argsJson] के साथ एक RevenueCat [action]; {ok, …} या {ok:false, error} के साथ [callbackId] को पूरा करता है।
उदाहरण
AppMint.revenuecat के पीछे का कच्चा मेथड - रैपर इस्तेमाल करें। एक एंट्री पॉइंट: action यह होता है: configure, getOfferings, purchase, restore, getCustomerInfo, logIn, logOut या getAppUserId.
लौटाता है: सीधे कुछ नहीं; रैपर जो जवाब देता है, उसी के साथ window.__appmintReplies[callbackId] को resolve करता है। ज़रूरत: Integrate चरण → आपकी public goog_ key के साथ RevenueCat।
window.__appmintReplies = window.__appmintReplies || {};
window.__appmintReplies['rc1'] = (r) => console.log(r.ok ? r.customerInfo : r.error);
WebToApk.revenuecat('getCustomerInfo', '{}', 'rc1');यूज़र id के साथ कॉन्फ़िगर करें और ऑफ़रिंग पढ़ें:
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');
}खरीदें और इनकार को संभालें:
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');ध्यान दें: हर एक्शन के आर्गुमेंट और जवाब के लिए AppMint.revenuecat देखें।
startRemoveAdsPurchase ब्रिज#
window.WebToApk.startRemoveAdsPurchase()
पेज से Google Play की "Remove Ads" खरीदारी शुरू करें (उपयोगकर्ता की ओर से, जैसे ऐप का अपना "Remove ads" बटन)। इसके बिना एकमात्र एंट्री पॉइंट नेटिव साइड-मेन्यू आइटम था - जो बिना साइड मेन्यू वाले ऐप में पहुँच से बाहर था। नतीजा पेज तक 'appmint:premium' {premium:true} के ज़रिए पहुँचता है; पहले से खरीदा हुआ हो तो तुरंत फिर सूचना देता है; फ़्लो की त्रुटियाँ नेटिव टोस्ट के रूप में दिखती हैं।
उदाहरण
आपके पेज के अपने "Remove ads" बटन से आपके "Remove Ads" प्रोडक्ट के लिए Google Play की खरीदारी शीट खोलता है।
लौटाता है: कुछ नहीं। नतीजा appmint:premium {premium: true} (और window.onAppMintPremiumChanged) के रूप में आता है। ज़रूरत: विज्ञापन चालू हों और Remove Ads (IAP) आपकी Play Store Product ID के साथ हो (सूची की पहली ID बेची जाती है)। जब Side Menu चालू हो, तो ऐप में वहाँ अपना Remove Ads आइटम भी होता है (उपयोगकर्ता के खरीदने पर छिप जाता है); साइड मेन्यू बंद हो तो आपके पेज का यह बटन ही खरीदने का एकमात्र तरीका है।
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;
});सेटिंग्स की एक पंक्ति जो दिखाती है कि विज्ञापन हटे हैं या नहीं, और साथ में उसे खरीदने वाला बटन:
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
}हर तीसरे खत्म हुए राउंड के बाद इसे ऑफ़र करें, ऐसे डायलॉग में जिसका जवाब खिलाड़ी टैप से देता है:
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(); });ध्यान दें: अगर यूज़र के पास यह पहले से है, तो appmint:premium {premium:true} तुरंत चलता है। त्रुटियाँ (Play तक पहुँच नहीं, Play Console में प्रोडक्ट नहीं मिला) ऐप एक छोटे नेटिव संदेश के रूप में दिखाता है; पेज को इनके लिए कोई इवेंट नहीं मिलता। Play की टेस्टिंग ट्रैक से इंस्टॉल किए गए ऐप के साथ टेस्ट करें।