API پل جاوااسکریپت
خریدها و دسترسیها
فروش از صفحه خودتان، و پرسیدن اینکه این خریدار از قبل مالک چه چیزی است. برای کد اطراف اینها راهنمای خریدهای درونبرنامهای را ببینید.
AppMint.revenuecat کمککننده#
AppMint.revenuecat — RevenueCat subscriptions
نمونه
اشتراکها و خریدهای یکباره از طریق پروژه RevenueCat شما: پیشنهادها، خریدها، بازیابیها و دسترسیها. خریدها در اپلیکیشن از طریق Google Play انجام میشوند؛ RevenueCat هر کدام را پیش از تمام شدن توسط اپلیکیشن ثبت میکند.
خروجی: هر متد یک Promise برمیگرداند که همیشه با {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}. نیازمندی: مرحله یکپارچهسازی ← RevenueCat با کلید عمومی 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. اتصال حساب سرویس Google Play را در RevenueCat همانطور که RevenueCat میخواهد تنظیم کنید؛ هرگز کلید محرمانه sk_ را در اپلیکیشن نگذارید. خریدهای AppMintIAP موجود بدون تغییر به کار خود ادامه میدهند.
AppMintIAP.available کمککننده#
window.AppMintIAP.available
نمونه
یک ویژگی (نه تابع): true وقتی این صفحه واقعاً میتواند از طریق Google Play بخرد.
خروجی: یک ویژگی بولی. نیازمندی: 💰 فروش محصولات درونبرنامهای. بدون آن، 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 آن را داخل یک افکت بخوانید (رندر سرور 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 پیش از اجرای اولین اسکریپت صفحه شما نصب میشود؛ فقط در WebViewهای قدیمیتر از حدود ۲۰۲۱ پس از اتمام بارگذاری صفحه میرسد، پس کدی که باید آنجا هم اجرا شود باید منتظر آن بماند (الگوی whenIap را در AppMintIAP.sell ببینید). همین مقدار در 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} میرسد. نیازمندی: 💰 فروش محصولات درونبرنامهای و محصول فعال در 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یک انتخابگر طرح با یک دکمه «ادامه» که هر طرحی را که انتخاب شده میخرد:
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. نیازمندی: 💰 فروش محصولات درونبرنامهای.
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 از طریق هر یک از چند طرح: یک ماهانه، یک سالانه و یک محصول مادامالعمر همگی حساب میشوند:
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). نیازمندی: 💰 فروش محصولات درونبرنامهای.
یک قلاب 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;یک صفحه پرداخت با جاوااسکریپت ساده: دو طرح را بدون دکمه ردیابی کنید، سپس قیمتها و طرح فعلی را از هر تغییر بکشید:
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 دسترسی حذف تبلیغات است (WebToApk.isPremium()). یک محصول فقط پس از پاسخ Google برای آن در products میآید.
AppMintIAP.owned کمککننده#
window.AppMintIAP.owned()
نمونه
هر شناسه محصولی را که کاربر مالک آن است فهرست میکند.
خروجی: آرایهای از شناسههای محصول، همزمان، مثلاً ['pro_unlock', 'level_pack_2']؛ بیرون از اپلیکیشن یا وقتی چیزی مالکیت نشده []. نیازمندی: 💰 فروش محصولات درونبرنامهای.
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);فهرست «خریدهای من» در صفحه تنظیمات شما، با نام خودتان برای هر محصول:
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() برایتان تجزیه میشود. اشتراکهای فعال و محصول حذف تبلیغات را شامل میشود. بدون اینترنت کار میکند.
AppMintIAP.refresh کمککننده#
window.AppMintIAP.refresh()
نمونه
دوباره دنبال دکمههای data-iap-buy میگردد که پس از آخرین اسکن اضافه شدهاند و آنها را متصل میکند (یا اگر هنوز چیزی پیدا نکرده بود کمکی را شروع میکند).
خروجی: شیء AppMintIAP. نیازمندی: 💰 فروش محصولات درونبرنامهای.
کمکی از قبل صفحه را برای دکمههای 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 (برای زنجیرهسازی). نیازمندی: 💰 فروش محصولات درونبرنامهای در ویزارد ساخت و محصول ایجادشده و فعال در Play Console. window.AppMintIAP پیش از اجرای اولین اسکریپت صفحه شما نصب میشود (در 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 بدهید (رابط خودتان AppMintIAP.state() را میخواند). opts دیگر: labelEl (عنصر داخل دکمه که متنش تغییر میکند). پیش یا پس از شروع کمکی کار میکند، از جمله از یک افکت React. همان ویژگیهای data-iap-buy (AppMintIAP.start را ببینید). محتوای پولی را فقط از روی مالکیت (isOwned، onChange) باز کنید، هرگز در کنترلکننده کلیک خودتان.
AppMintIAP.start کمککننده#
window.AppMintIAP.start(config) and the data-iap-* attributes
نمونه
دکمههای خرید شما را در یک مرحله به Google Play وصل میکند: قیمت زنده روی دکمهها، صفحه خرید با لمس، برچسبهای «خریداریشده»، بلوکهای تشکر، بازیابی هنگام اجرا، و پیام قابل مشاهده برای هر شکست.
خروجی: شیء AppMintIAP (برای زنجیرهسازی). نیازمندی: 💰 فروش محصولات درونبرنامهای در ویزارد ساخت و شناسههای محصول ایجادشده و فعال در Play Console. اپلیکیشن window.AppMintIAP را پیش از اجرای اولین اسکریپت صفحه شما نصب میکند (در 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 ندارند (با آنها کمکی از قبل شروع شده و شروع دوم کاری نمیکند):
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 رندر کند که تابع لغو اشتراک را برای افکت برمیگرداند:
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 (برچسب پس از مالکیت؛ پیشفرض «✓ خریداری شد»)، data-iap-group (طرحهایی که جایگزین هم میشوند؛ پس از مالکیت یکی، بقیه پنهان میشوند مگر hideOtherPlans: false)، data-iap-show / data-iap-hide (مقدار = شناسه محصول یا گروه؛ خالی = هر چیز مالکیتشده)، data-iap-text (بخشی از دکمه که متنش تغییر میکند)، data-iap-container. دکمه مالکیتشده data-iap-state="owned" و کلاس iap-unlocked میگیرد. لغو در صفحه Google رویدادی نمیفرستد؛ دکمه وقتی صفحه دوباره دیده شد (یا پس از ۳۰ ثانیه) آزاد میشود. فراخوانی start(options) پس از آنکه کمکی خودش از ویژگیهای data-iap-buy شروع شده، گزینهها را (طرحها، نمایش/پنهان، ظرف، onUnlock/onLock، toast، busyLabel، hideOtherPlans) با مجموعه در حال اجرا ادغام میکند؛ دو بار فراخوانی بیضرر است. صفحهای که appmint-iap.js خودش را دارد آن را نگه میدارد - نسخه داخلی کنار میرود.
AppMintIAP.started کمککننده#
window.AppMintIAP.started
نمونه
یک ویژگی (نه تابع): true وقتی کمکی در حال اجراست، یعنی دستکم یک محصول را از دکمههای start()، sell() یا data-iap-buy متصل کرده است.
خروجی: یک ویژگی بولی. نیازمندی: 💰 فروش محصولات درونبرنامهای.
از آن برای انتخاب بین 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 آرایهای از شناسههای محصول است؛ products برای محصولاتی که کمکی از Google دربارهشان پرسیده {id: {price, currency, type: 'inapp'|'subs', period, owned}} است. نیازمندی: 💰 فروش محصولات درونبرنامهای.
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);
}وقتی دسترسی بدون تبلیغ ساخت فعال شد، پیشنهادهای «بدون تبلیغ شوید» خودتان را پنهان کنید و قیمتهایی را که 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
آرایه JSON از همه شناسههای محصول مالکیتشده - برای قفل کردن کل صفحهها هنگام شروع.
نمونه
همه محصولات Play را که کاربر مالک آنهاست فهرست میکند، تا هنگام شروع کل صفحهها را باز کنید.
خروجی: یک رشته JSON (آرایه شناسههای محصول)، همزمان، مثلاً '["pro_unlock","level_pack_2"]'. آن را با JSON.parse تجزیه کنید. وقتی چیزی مالکیت نشده '[]'. نیازمندی: 💰 فروش محصولات درونبرنامهای در ویزارد ساخت.
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) را تعریف کرده باشید آن را هم فراخوانی میکند.
فهرست «خریدهای من» در صفحه تنظیمات شما، با نام خودتان برای هر محصول:
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()); });نکتهها: شامل محصولات یکباره و اشتراکهای فعال است، و محصول حذف تبلیغات هم اگر کاربر خریده باشد. بدون اینترنت کار میکند؛ فهرست در هر بازیابی هنگام اجرا و هر خرید روی گوشی ذخیره میشود. appmint:owned-changed فقط وقتی فعال میشود که فهرست واقعاً تغییر کرده باشد، پس ممکن است در اجرای عادی اصلاً فعال نشود.
getProducts پل#
window.WebToApk.getProducts(requestId: String, productIdsJson: String)
جزئیات زنده شناسههای محصول دادهشده (آرایه JSON از رشتهها) را از Play میخواهد. نتیجه بهصورت 'appmint:products' {requestId, products:[{productId, type, title, description, price, currency, period?, owned}]} میرسد. شناسههایی که Play نمیشناسد در نتیجه نیستند - صفحه آن آیتمها را پنهان میکند.
نمونه
جزئیات زنده محصولات شما را از Google Play میخواهد: عنوان، توضیح و قیمت محلیشده.
خروجی: فعلاً هیچ. پاسخ بهصورت رویداد appmint:products میرسد: {requestId, products: [{productId, type, title, description, price, currency, period?, owned}]}. type برابر 'inapp' یا 'subs' است؛ period یک دوره ISO-8601 مانند 'P1M' است (فقط اشتراکها). نیازمندی: 💰 فروش محصولات درونبرنامهای در ویزارد ساخت. آرگومان دوم یک رشته 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']));
}هنگام آزمایش، شناسههایی را که 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 میدهد نشان دهید و هرگز ذخیره نکنید. شناسهای که Play نمیشناسد (ایجاد نشده، فعال نیست، یا اپلیکیشن از Play نصب نشده) در products نمیآید؛ آن آیتم را پنهان نگه دارید. یک requestId شبیه عدد بهصورت عدد برمیگردد، پس از شناسه متنی استفاده کنید یا مقایسه کنید با String().
isOwned پل#
window.WebToApk.isOwned(productId: String): Boolean
بررسی فوری مالکیت روی دستگاه (محصول یکباره مالکیتشده یا اشتراک فعال).
نمونه
فوراً و بدون اینترنت بررسی میکند که کاربر مالک یکی از محصولات Play شما هست یا نه (یک محصول یکباره، یا اشتراکی که هنوز فعال است).
خروجی: true / false، همزمان. نیازمندی: 💰 فروش محصولات درونبرنامهای در ویزارد ساخت. با روشن بودن آن کلید، برای محصول حذف تبلیغات (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
وقتی کاربر مالک «حذف تبلیغات» است درست است (دسترسی Play Billing، در هر اجرا خودکار بازیابی میشود). با دلیل 'ad_free' در 'appmint:ad-unavailable'.
نمونه
به شما میگوید کاربر مالک محصول «حذف تبلیغات» شما هست یا نه، تا صفحه بتواند دکمه «حذف تبلیغات» و رابط مربوط به تبلیغات را پنهان کند.
خروجی: true / false، همزمان. نیازمندی: تبلیغات روشن و حذف تبلیغات (IAP) با شناسه محصول Play Store شما. بدون آن همیشه 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}پیشنهاد «حذف تبلیغات» خودتان را در صفحه پایان بازی فقط به بازیکنانی نشان دهید که هنوز تبلیغ میبینند:
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 را دنبال کنید. فیلد شناسه محصول چند شناسه جداشده با کاما میپذیرد (مثلاً یک طرح ماهانه و یک مادامالعمر)؛ مالکیت هر کدام حساب میشود. در حالت پریمیوم، تبلیغات حذف میشوند و فراخوانیهای تبلیغ پاسخ میدهند ad_free.
purchase پل#
window.WebToApk.purchase(productId: String)
صفحه خرید Google را برای [productId] باز میکند (محصول یکباره یا اشتراک - خودکار تشخیص داده میشود). فقط روی 'appmint:purchase' {productId, owned:true} اعطا کنید؛ شکستها 'appmint:purchase-failed' {reason} را فعال میکنند. اگر از قبل مالکیتشده باشد، رویداد خرید دوباره فعال میشود تا صفحه بتواند بازگشایی را تکرارپذیر انجام دهد.
نمونه
صفحه خرید Google Play را برای یکی از محصولات شما باز میکند (محصول یکباره یا اشتراک؛ اپلیکیشن خودش تشخیص میدهد).
خروجی: فعلاً هیچ. موفقیت appmint:purchase {productId, owned: true} را فعال میکند. شکست appmint:purchase-failed {productId, reason} را فعال میکند. نیازمندی: 💰 فروش محصولات درونبرنامهای در ویزارد ساخت، و محصول ایجادشده و فعال در 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) را هم فراخوانی میکنند.
طرحی را که کاربر در انتخابگر اشتراک برگزیده بخرید و طرحی را که 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 (فروش محصولات درونبرنامهای خاموش است)، not_found (شناسه در Play Console نیست)، billing_error، pending (کاربر با روش کند پرداخت کرده؛ بازگشایی بعداً از طریق بازیابی هنگام اجرا و appmint:owned-changed میرسد). اگر محصول از قبل مالکیتشده باشد، appmint:purchase فوراً دوباره فعال میشود، پس بازگشایی شما باید تکرار بیخطر باشد. اپلیکیشن برای خطاها پیام کوتاه خودش را هم نشان میدهد.
revenuecat پل#
window.WebToApk.revenuecat(action: String, argsJson: String, callbackId: String)
یک [action] از RevenueCat با [argsJson]؛ [callbackId] را با {ok, …} یا {ok:false, error} پاسخ میدهد.
نمونه
متد خام پشت AppMint.revenuecat - از پوششدهنده استفاده کنید. یک نقطه ورود: action یکی از configure، getOfferings، purchase، restore، getCustomerInfo، logIn، logOut است یا getAppUserId.
خروجی: مستقیماً هیچ؛ window.__appmintReplies[callbackId] را با همان پاسخی که پوششدهنده میدهد حل میکند. نیازمندی: مرحله یکپارچهسازی ← RevenueCat با کلید عمومی goog_ شما.
window.__appmintReplies = window.__appmintReplies || {};
window.__appmintReplies['rc1'] = (r) => console.log(r.ok ? r.customerInfo : r.error);
WebToApk.revenuecat('getCustomerInfo', '{}', 'rc1');با شناسه کاربر پیکربندی کنید و پیشنهادها را بخوانید:
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 را از صفحه اجرا میکند (به ابتکار کاربر، مثلاً دکمه «حذف تبلیغات» خود اپلیکیشن). بدون این، تنها نقطه ورود آیتم بومی منوی کناری بود - که در اپلیکیشنهای ساختهشده بدون منوی کناری در دسترس نیست. نتیجه از طریق 'appmint:premium' {premium:true} به صفحه میرسد؛ اگر از قبل مالک باشد، فوراً دوباره اطلاع میدهد؛ خطاهای جریان بهصورت پیام کوتاه بومی نمایش داده میشوند.
نمونه
صفحه خرید Google Play را برای محصول «حذف تبلیغات» شما باز میکند، از دکمه «حذف تبلیغات» خود صفحه.
خروجی: هیچ. نتیجه بهصورت appmint:premium {premium: true} (و window.onAppMintPremiumChanged) میرسد. نیازمندی: تبلیغات روشن و حذف تبلیغات (IAP) با شناسه محصول Play Store شما (اولین شناسه فهرستشده همان است که فروخته میشود). وقتی منوی کناری روشن است، اپلیکیشن خودش هم آیتم حذف تبلیغات را آنجا دارد (وقتی کاربر مالک شد پنهان میشود)؛ با منوی کناری خاموش، این دکمه در صفحه شما تنها راه خرید است.
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 نصب شده آزمایش کنید.