کسب درآمد
فروش محصولات درونبرنامهای (IAP)
خریدهای Google Play از وبسایت یا اپلیکیشن ZIP خودتان
1📥 راهنما و اپلیکیشن دموی کارا را بگیرید#
همهٔ مطالب این راهنما بهصورت PDF هم هست که میتوانید آفلاین بخوانید، بهعلاوهٔ یک اپلیکیشن دمو کامل که امروز میتوانید بسازید و اجرا کنید.
زیر را بزنید تا هر دو در گوشی شما زیر Downloads/AppMint/Guides ذخیره شوند:
- AppMint-InApp-Purchases-Guide.pdf - راهنمای کامل، شامل مراحل Play Console و مثالهای React / Next.js
- iap-demo-index.html - یک بازی کوچک که سه چیز میفروشد: حذف تبلیغات (یکباره)، یک بستهٔ مرحله (یکباره) و یک بلیت فصلی ماهانه
برای امتحان دمو: iap-demo-index.html را بهتنهایی ZIP کنید، در stepper بارگذاری کنید و راهنما را دنبال کنید. سریعترین راه برای دیدن یک خرید واقعی از ابتدا تا انتها پیش از دست زدن به اپلیکیشن خودتان است.
در اپلیکیشن، دکمه این مرحله اینها را روی گوشی شما ذخیره میکند. اینجا دانلود مستقیم هستند.
AppMint-InApp-Purchases-Guide.pdf iap-demo-index.html2این چیست؟ (به زبان ساده)#
اپلیکیشن شما میتواند چیزهایی بفروشد - ارتقای پرو، مرحلههای اضافه، اشتراک - و Google پول را جمع میکند و هر ماه به شما میپردازد.
یک مثال را با هم میسازیم، در سرتاسر این راهنما:
A game that sells two things
• 'Levels 11-20' ₹99 paid once, kept forever
• 'Remove ads' ₹49 every month
در پایان فقط چهار خط به صفحهٔ خود اضافه میکنید. تمام کار همین است - لازم نیست کد خرید بنویسید، و حتی اگر تا حالا این کار را نکردهاید، مشکلی پیش نمیآید.
لازم نیست بیش از کپی و جایگذاری برنامهنویسی بدانید، و به سرور، پایگاه داده یا حساب شرکت پرداخت هم نیاز ندارید. Google Play کارهای سخت را انجام میدهد:
- صفحهٔ پرداخت را نشان میدهد (کارت / UPI / صورتحساب اپراتور)
- قیمت شما را به ارز هر کشور تبدیل میکند
- به یاد میآورد چه کسی چه چیزی خریده - حتی بعد از نصب دوباره
- پول را به بانک شما میفرستد
AppMint ۰٪ برمیدارد - فروش مستقیم به حساب Google Play خودتان میرود.
برای Website → APK، ZIP → APK و اپلیکیشنهای ساختهشده با هوش مصنوعی یکسان کار میکند.
3پیش از شروع به چه چیزی نیاز دارید#
فقط چهار چیز:
- وبسایت یا اپلیکیشن HTML/ZIP شما (چیزی که به APK تبدیل میکنید).
- چیزی برای فروش - تصمیم بگیرید خریداران چه چیزی دریافت میکنند (مثلاً «نسخهٔ پرو»: بدون محدودیت، محتوای اضافه).
- یک حساب توسعهدهندهٔ Google Play - هزینهٔ یکباره ۲۵ دلار در play.google.com/console. برای انتشار هر اپلیکیشنی در Play Store به آن نیاز دارید، نه فقط اپلیکیشنهای پولی.
- در مجموع حدود ۱ ساعت، پخششده در مرحلههای زیر.
دانستن این از ابتدا مهم است: خریدهای درونبرنامهای فقط وقتی کار میکنند که اپلیکیشن شما از Play Store نصب شده باشد. این قانون Google برای همه است، نه محدودیت AppMint.
4مرحلهٔ ۱ - شناسهٔ محصول خود را بسازید#
شناسهٔ محصول فقط یک نام کوتاه است که برای چیزی که میفروشید میسازید. بعداً همین نام را در دو جا تایپ میکنید (صفحهٔ شما + Play Console)، پس ساده نگهش دارید و یادداشت کنید.
قوانین: فقط حروف کوچک، اعداد، زیرخط. باید با حرف یا عدد شروع شود.
یکی از اینها را کپی کنید:
- pro_unlock - ارتقای «نسخهٔ پرو» (پیشفرض خوب - کد زیر از قبل از آن استفاده میکند)
- remove_ads - ارتقای بدون تبلیغ
- level_pack_2 - بستهٔ محتوای اضافه
- coins_100 - بستهٔ ارز بازی
- premium_monthly - اشتراک ماهانه
قیمت بخشی از شناسه نیست - قیمت را در مرحلهٔ ۵ تعیین میکنید و میتوانید هر زمان بدون دست زدن به کد آن را تغییر دهید.
5مرحلهٔ ۲ - دکمهٔ خود را علامت بزنید (بدون کد نوشتن)#
لازم نیست هیچ کد خریدی بنویسید. اپلیکیشن شما از قبل موتور خرید را دارد - فقط HTML خودتان را برچسب میزنید تا بداند چه بفروشد.
بلوک زیر را در صفحهٔ خود کپی کنید و فقط اینها را تغییر دهید:
(۱) pro_unlock ← شناسهٔ محصول شما، در data-iap-buy
(۲) کلمات روی دکمه و داخل بخش پولی
همین. بعد دکمه قیمت واقعی Google را به ارز خود خریدار نشان میدهد، با زدن صفحهٔ پرداخت Google را باز میکند، بخش پولی را پس از پرداخت آشکار میکند (و در هر راهاندازی برای کسانی که قبلاً خریدهاند)، اگر اشتراک منقضی شود یا پرداخت بازپرداخت شود آن را پس میگیرد، و اگر مشکلی پیش بیاید پیام روشنی نشان میدهد.
چهار برچسب:
- data-iap-buy="id" - این را دکمهٔ خرید میکند (الزامی)
- data-iap-label="…{price}…" - متن دکمه؛ {price} به قیمت واقعی Google تبدیل میشود
- data-iap-owned="…" - متن دکمه بعد از خرید
- data-iap-show - بخشی که فقط پس از خرید ظاهر میشود (آن را 'hidden' شروع کنید)
همچنین موجود است: data-iap-hide (بعد از خرید ناپدید میشود)، data-iap-container (یک پوشش که فقط داخل اپلیکیشن ظاهر میشود)، data-iap-text (اگر دکمهتان آیکون هم دارد، آن را روی <span> حاوی متن بگذارید).
چند چیز مختلف میفروشید؟ برای هر کدام یک دکمه اضافه کنید. آنها مستقلاند - یک اپلیکیشن میتواند تبلیغات را حذف کند و بستهٔ مرحله بفروشد و اشتراک هم داشته باشد، و خرید یکی هرگز روی بقیه اثر نمیگذارد. یک بخش را با data-iap-show="level_pack_2" به یک محصول وصل کنید.
فقط شناسههایی که در «Remove Ads (IAP) ← Product ID» تایپ میکنید تبلیغات را خاموش میکنند. هر چیز دیگری که میفروشید هر کاری را که صفحهتان با آن میکند باز میکند و تبلیغات ادامه دارند.
دو طرح برای یک چیز (ماهانه در برابر دائمی)؟ به هر دو دکمه data-iap-group="adfree" یکسان بدهید - آنوقت خرید هر کدام دیگری را پنهان میکند، پس هیچکس دوبار پرداخت نمیکند. بدون group چیزی پنهان نمیشود، که برای محصولات نامرتبط همین لازم است.
<!-- (1) CHANGE pro_unlock to your product ID -->
<button data-iap-buy="pro_unlock"
data-iap-label="Unlock Pro — {price}"
data-iap-owned="✓ Unlocked">
Unlock Pro
</button>
<!-- (2) CHANGE the words. Appears only after buying. -->
<div data-iap-show hidden>
🎉 Pro content here — visible after buying.
</div>
<!-- Optional: disappears after buying -->
<div data-iap-hide>
Upgrade to remove the ads.
</div>
<!-- DIFFERENT THINGS: independent — buying one never affects the others -->
<button data-iap-buy="remove_ads" data-iap-label="Remove ads — {price}">Remove ads</button>
<button data-iap-buy="level_pack_2" data-iap-label="Levels 11-20 — {price}">Get levels</button>
<div data-iap-show="level_pack_2" hidden>Levels 11-20 unlocked!</div>
<!-- SAME THING, TWO PLANS: one group, so buying either hides the other -->
<button data-iap-buy="remove_ads_monthly" data-iap-group="adfree"
data-iap-label="Remove ads — {price}/month"
data-iap-owned="✓ Ad-free (monthly)">Remove ads monthly</button>
<button data-iap-buy="remove_ads_lifetime" data-iap-group="adfree"
data-iap-label="Remove ads forever — {price}"
data-iap-owned="✓ Ads removed forever">Remove ads forever</button>6۴ چیزی که مردم معمولاً میفروشند#
هر کدام را که با شما میخواند انتخاب کنید. تنها تفاوت آنها محل ساخت محصول در Play Console است - کد صفحهٔ شما هر بار همان شکل را دارد.
- حذف تبلیغات، یکبار پرداخت (برای همیشه)
آن را اینجا بسازید: In-app products
شناسه را همچنین در اینجا تایپ کنید: Remove Ads (IAP) ← Product ID
- حذف تبلیغات، ماهانه
آن را اینجا بسازید: Subscriptions
شناسه را همچنین در اینجا تایپ کنید: Remove Ads (IAP) ← Product ID
- باز کردن مرحلهها / محتوا، یکبار پرداخت
آن را اینجا بسازید: In-app products
آن را در کادر Remove Ads تایپ نکنید - تبلیغات شما ادامه مییابند.
- باز کردن مرحلهها / محتوا، ماهانه
آن را اینجا بسازید: Subscriptions
آن را در کادر Remove Ads تایپ نکنید - تبلیغات شما ادامه مییابند.
میتوانید هر چهار مورد را در یک اپلیکیشن بفروشید، و هر تعداد محصول که بخواهید. خرید یکی هرگز روی بقیه اثر نمیگذارد.
<!-- 1. remove ads, paid once -->
<button data-iap-buy="remove_ads_lifetime"
data-iap-label="Remove ads forever — {price}"
data-iap-owned="✓ Ads removed">Remove ads forever</button>
<!-- 2. remove ads, monthly -->
<button data-iap-buy="remove_ads_monthly"
data-iap-label="Remove ads — {price}/month"
data-iap-owned="✓ Ad-free">Remove ads monthly</button>
<!-- 3. levels, paid once -->
<button data-iap-buy="level_pack_2"
data-iap-label="Levels 11-20 — {price}"
data-iap-owned="✓ Unlocked">Get levels 11-20</button>
<!-- 4. all levels, monthly -->
<button data-iap-buy="all_levels_monthly"
data-iap-label="All levels — {price}/month"
data-iap-owned="✓ Subscribed">Subscribe</button>7کدام خریدها تبلیغات را حذف میکنند؟#
این همان چیزی است که مردم اشتباه میکنند، پس ۳۰ ثانیه ارزش دارد.
تبلیغات شما بخشی از صفحهٔ وب شما نیستند. آنها تبلیغات واقعی اندروید هستند که روی آن قرار میگیرند، پس هیچ چیز در صفحهٔ شما نمیتواند آنها را خاموش کند. فقط یک چیز میتواند:
wizard Step 3 → Remove Ads (IAP) → Product ID
هر شناسهای که در آن کادر بنویسید، وقتی کسی آن را بخرد تبلیغات را حذف میکند. هر شناسهٔ دیگری که میفروشید فقط هر کاری را که صفحهتان با آن میکند باز میکند، و تبلیغات ادامه دارند.
هم یک طرح بدون تبلیغ و هم بستهٔ مرحله میفروشید؟ فقط شناسهٔ طرح بدون تبلیغ را در آن کادر بنویسید. در مثال ما:
Product ID box: remove_ads_monthly
NOT in the box: level_pack_2
دو طرح بدون تبلیغ (ماهانه و دائمی) ارائه میدهید؟ هر دو را در همان یک کادر بگذارید، با کاما جدا کنید - مالکیت هر کدام تبلیغات را حذف میکند:
remove_ads_monthly,remove_ads_lifetime
اصلاً حذف تبلیغات را نمیفروشید؟ آن کلید را کاملاً خاموش بگذارید و فقط از «💰 Sell In-App Products» استفاده کنید.
Selling levels only:
Remove Ads (IAP) OFF
Sell In-App Products ON
Selling ad removal only:
Remove Ads (IAP) ON → Product ID: remove_ads_monthly
Sell In-App Products ON
Selling both, with two ad-free plans:
Remove Ads (IAP) ON → Product ID: remove_ads_monthly,remove_ads_lifetime
Sell In-App Products ON
(level_pack_2 stays out of the box)8مثال کامل - صفحهٔ HTML ساده#
این هم کل اپلیکیشن مثال ما: «Levels 11-20» را یکبار میفروشد، و «Remove ads» را ماهانه. آن را کپی کنید، دو شناسه و کلمات را تغییر دهید، و تمام.
بخوانید هر برچسب چه کاری میکند:
- data-iap-buy ← این دکمه چه چیزی میفروشد
- data-iap-label ← متن؛ {price} به قیمت واقعی Google تبدیل میشود
- data-iap-owned ← متن بعد از پرداخت
- data-iap-show="level_pack_2" ← این کادر فقط بعد از خرید همان محصول ظاهر میشود
- data-iap-hide ← این کادر با خرید هر چیزی ناپدید میشود
توجه کنید که هیچ <script> و هیچ کد خریدی نیست. اپلیکیشن شما از قبل آن را دارد.
<h2>Get more levels</h2>
<button data-iap-buy="level_pack_2"
data-iap-label="Levels 11-20 — {price}"
data-iap-owned="✓ Levels 11-20 unlocked">
Levels 11-20
</button>
<div data-iap-show="level_pack_2" hidden>
🎉 Levels 11 to 20 are yours. Have fun!
</div>
<h2>Tired of ads?</h2>
<button data-iap-buy="remove_ads_monthly"
data-iap-label="Remove ads — {price}/month"
data-iap-owned="✓ No more ads">
Remove ads
</button>
<div data-iap-hide>
Ads help keep this game free.
</div>9مثال کامل - React یا Next.js#
روش آسان: دقیقاً همین برچسبها در React و Next.js کار میکنند، چون فقط نشانهگذاریاند. بدون useState، بدون useEffect، بدون 'use client'. دکمههایی که React بعداً اضافه میکند خودکار شناسایی میشوند.
بیشتر اپلیکیشنها فقط همین را لازم دارند - از بلوک اول زیر استفاده کنید و همانجا بایستید.
روش کنترلشده (بلوک دوم): فقط اگر میخواهید خود React قیمت را نشان دهد و صفحه را عوض کند. در این صورت:
- در Next.js، 'use client' را در خط ۱ فایل بگذارید وگرنه ساخت شکست میخورد.
- فقط داخل useEffect به window.AppMintIAP دست بزنید - در بدنهٔ کامپوننت روی سرور اجرا میشود که window در آن وجود ندارد.
- onChange(fn) فوراً وضعیت را به شما میدهد و دوباره در هر تغییر، و تابع لغو اشتراکی را که React میخواهد برمیگرداند.
- در Next.js بهصورت خروجی ایستا بسازید: output: 'export' در next.config.js.
// ── EASY WAY — just JSX, nothing else ──────────────────────────
export default function Shop() {
return (
<>
<button data-iap-buy="level_pack_2"
data-iap-label="Levels 11-20 — {price}"
data-iap-owned="✓ Unlocked">Levels 11-20</button>
<div data-iap-show="level_pack_2" hidden>🎉 Levels 11-20 are yours!</div>
</>
);
}
// ── CONTROLLED WAY — React draws everything ───────────────────
'use client'; // Next.js: must be line 1
import { useEffect, useState } from 'react';
export default function Shop() {
const [iap, setIap] = useState({ available: false, products: {} });
useEffect(() => {
if (typeof window === 'undefined' || !window.AppMintIAP) return;
window.AppMintIAP.start({
plans: [{ id: 'level_pack_2' }, { id: 'remove_ads_monthly' }]
});
return window.AppMintIAP.onChange(setIap);
}, []);
if (!iap.available) return null; // outside the app there is nothing to buy
const levels = iap.products?.level_pack_2; // always optional-chain
return levels?.owned
? <p>🎉 Levels 11-20 are yours!</p>
: <button onClick={() => window.AppMintIAP.buy('level_pack_2')}>
Levels 11-20 {levels?.price}
</button>;
}10بررسی سریع در مرورگر#
صفحهٔ خود را در یک مرورگر معمولی باز کنید (Chrome روی کامپیوتر یا گوشی).
باید دکمهٔ خرید را ببینید که آنجا است و کاری نمیکند، و بخش پولی شما هنوز پنهان است. هر دو درستاند: بیرون از اپلیکیشن چیزی برای خرید نیست، پس چیزی بهطور اتفاقی خریده نمیشود.
داخل اپلیکیشن کاملاً صفحهٔ دیگری است: دکمه قیمت واقعی را میگیرد، صفحهٔ پرداخت Google را باز میکند و وقتی خریدار مالک شد خودش را پنهان میکند. وبسایت شما در هر دو حالت برای بازدیدکنندگان عادی طبق معمول کار میکند.
میخواهید پیشنهاد در وب کاملاً ناپدید شود؟ آن را در <div data-iap-container hidden> بپیچید - آن پوشش فقط داخل اپلیکیشن آشکار میشود.
11مرحلهٔ ۳ - خریدها را در STEPPER روشن کنید#
⚠️ این را رد نکنید. اگر رد کنید دکمههایتان باز هم ظاهر میشوند، اما هر زدن پاسخ «In-app purchases are not enabled in this app.» میدهد و پولی نمیگیرید.
STEPPER همان صفحههای Step 1 ← Step 2 ← Step 3 است که برای ساخت اپلیکیشن استفاده میکنید. خریدها در STEPPER STEP 3 هستند.
به ترتیب انجام دهید:
- stepper را باز کنید و به STEPPER STEP 3 بروید (صفحهای که Ads، Notifications و Permissions دارد).
STEPPER STEP 3 — what to set
Ads ......................... ON (needed to see the card below)
💰 Sell In-App Products ..... ON ← REQUIRED for selling anything
Selling ad removal too?
Remove Ads (IAP) ............ ON
Play Store Product ID ....... remove_ads_monthly
(two plans) .......... remove_ads_monthly,remove_ads_lifetime
License Key ................. paste from Play Console
Export ...................... AAB (not APK)12مرحلهٔ ۴ - اپلیکیشن خود را روی Google Play بگذارید#
در Play Console خود (وبسایتی که توسعهدهندگان اپلیکیشنهایشان را مدیریت میکنند):
- «Create app» ← نام و پرسشهای پایه را پر کنید.
- در منوی سمت چپ: «Testing» ← «Internal testing» ← «Create new release» ← فایل AAB را بارگذاری کنید ← «Save» و منتشر کنید.
- همچنان در Internal testing ← تب «Testers» ← Gmail خودتان را اضافه کنید ← لینک «join» را برای بعد کپی کنید.
Internal testing یک محیط خصوصی است - هنوز هیچکس اپلیکیشن شما را نمیبیند. برای این است که خودتان بتوانید پیش از عمومی شدن خریدها را امتحان کنید.
13مرحلهٔ ۵ - محصول خود را بسازید و قیمت را تعیین کنید#
حالا به Google بگویید چه چیزی میفروشید. اینکه از کدام منو استفاده کنید بستگی دارد مشتری یکبار پرداخت میکند یا هر ماه.
یکبار پرداخت (مرحلهها، پرو، سکه، حذف دائمی تبلیغات)
- Play Console ← اپلیکیشن شما ← «Monetize» ← «Products» ← «In-app products».
- «Create product».
- Product ID: دقیقاً همان شناسهای را که در صفحهتان است تایپ کنید - level_pack_2. همان املا، همان حروف بزرگ و کوچک. Google هرگز اجازه نمیدهد آن را تغییر دهید یا دوباره استفاده کنید.
- Name: آنچه خریداران میبینند، مثلاً «Levels 11-20». Description: یک خط.
- قیمت را به ارز خودتان تعیین کنید - Google آن را برای هر کشور تبدیل میکند.
- «Activate» را بزنید.
هر ماه (حذف ماهانهٔ تبلیغات، اشتراک همهٔ مرحلهها)
- Play Console ← «Monetize» ← «Products» ← «Subscriptions».
- «Create subscription». Product ID: remove_ads_monthly.
- ذخیره کنید، سپس یک BASE PLAN اضافه کنید: نوع «Auto-renewing»، دورهٔ صورتحساب «Monthly»، قیمت شما.
- روی base plan و روی خود اشتراک «Activate» را بزنید. اگر یکی را جا بیندازید، اپلیکیشن شما آن را نمیبیند.
برای هر شناسهای که استفاده کردید تکرار کنید. کد صفحهٔ شما تغییر نمیکند - اپلیکیشن از Google میپرسد هر شناسه از کدام نوع است.
ماهانه *و* سالانه میخواهید؟ دو اشتراک جداگانه بسازید (all_levels_monthly و all_levels_yearly) بهجای دو base plan داخل یک اشتراک. یک اشتراک با چند base plan همیشه اولی را ارائه میدهد، بدون راهی برای انتخاب.
هر قیمتی را بعداً در Play Console تغییر دهید - اپلیکیشن شما خودش قیمت جدید را نشان میدهد، بدون نیاز به ساخت دوباره.
14مرحلهٔ ۶ - آزمایش کنید (از شما پولی کم نمیشود)#
- Play Console ← منوی چپ «Setup» ← «License testing» ← Gmail خود را به فهرست آزمایشکنندگان اضافه کنید.
- روی گوشی: لینک join مربوط به internal-testing را از مرحلهٔ ۴ باز کنید، بپذیرید، و اپلیکیشن را از Play Store نصب کنید.
- اپلیکیشن را باز کنید. دکمهٔ خرید شما با قیمت واقعی ظاهر میشود.
- روی آن بزنید - صفحهٔ پرداخت Google باز میشود و میگوید «Test card, always approves». بخرید. محتوای پرو شما ظاهر میشود. چیزی پرداخت نمیکنید.
- بررسی جایزه: اپلیکیشن را حذف و دوباره نصب کنید - محتوای پرو هنگام راهاندازی خودبهخود باز میشود. این Google است که خرید را بازیابی میکند.
⚠️ اگر APK را مستقیم نصب کنید (sideload) و نه از Play Store، دکمهٔ خرید ظاهر نمیشود. این عادی است - همیشه از طریق لینک Play Store آزمایش کنید.
15مرحلهٔ ۷ - انتشار و دریافت پول#
- وقتی آزمایش خوب بود: Play Console ← «Production» ← با همان AAB یک انتشار بسازید ← منتشر کنید. (اپلیکیشنهای بار اول از بررسی Google میگذرند - معمولاً چند روز.)
- کاربران داخل اپلیکیشن شما میخرند ← Google پول را جمع میکند.
- Google هر ماه به حساب بانکی شما میپردازد (Play Console ← «Payments profile»). Google کارمزد استاندارد خود را نگه میدارد (۱۵٪ برای بیشتر توسعهدهندگان). AppMint چیزی نگه نمیدارد.
برای افزودن محصولات بیشتر بعداً: شناسهٔ جدید را در کد صفحه اضافه کنید، یکبار دوباره بسازید و شناسه را در Play Console بسازید. تغییر قیمت هرگز به ساخت دوباره نیاز ندارد.
16اگر چیزی کار نکرد#
اپلیکیشن شما بهجای شکست بیصدا، میگوید چه مشکلی پیش آمده. پیامی را که میبینید پیدا کنید:
«In-app purchases are not enabled in this app.»
← هنگام ساخت، «💰 Sell In-App Products» در STEPPER STEP 3 خاموش بود. آن را روشن کنید و دوباره بسازید.
«This item is not available on Google Play yet.»
← Google آن شناسه را نمیشناسد. حرفبهحرف با Play Console مقایسه کنید، مطمئن شوید محصول ACTIVE است (برای اشتراک، base plan و اشتراک هر دو باید فعال باشند)، و مطمئن شوید اپلیکیشن را از لینک آزمایشکنندهٔ Play نصب کردهاید.
«Google Play could not start the payment.»
← اپلیکیشن از Play Store نصب نشده بود، یا با کلیدی غیر از آنچه بارگذاری کردید امضا شده بود.
«Payment pending - this unlocks automatically once Google confirms.»
← کاری لازم نیست. روشهای پرداخت کند (کوپن نقدی، برخی UPI) چند دقیقه طول میکشند؛ باز شدن خودبهخود میرسد، حتی پس از راهاندازی مجدد اپلیکیشن.
اصلاً هیچ پیامی نیست، با زدن هیچ اتفاقی نمیافتد
← تقریباً همیشه غلط تایپی در data-iap-buy است، یا اپلیکیشن از Play نصب نشده است. هر دلیل دیگری یکی از پیامهای بالا را نشان میدهد.
پرداخت انجام شد اما تبلیغات هنوز هستند
← آن شناسهٔ محصول در STEPPER STEP 3 ← «Remove Ads (IAP)» ← «Play Store Product ID» نیست. آن را اضافه کنید (چند مورد را با کاما جدا کنید) و دوباره بسازید.
دکمهٔ خرید هرگز در اپلیکیشن ظاهر نمیشود
← «💰 Sell In-App Products» هنگام ساخت خاموش بود. در مرورگر معمولی دکمهها عمداً پنهاناند - بیرون از اپلیکیشن چیزی برای خرید نیست.
بعد از بستن صفحهٔ پرداخت Google هیچ اتفاقی نیفتاد
← این یک لغو عادی است. دوباره بزنید و پرداخت را کامل کنید.
قیمت قدیمی به نظر میرسد
← Play قیمتها را چند ساعت روی دستگاه ذخیره میکند. نصب تازه همیشه قیمت جدید را نشان میدهد.
17React، Next.js یا JavaScript ساده#
React و Next.js به چیز خاصی نیاز ندارند. برچسبهای بالا فقط نشانهگذاریاند، پس همان یک خط آنجا هم کار میکند - بدون useState، بدون useEffect، بدون 'use client':
<button data-iap-buy="pro_unlock"
data-iap-label="Unlock Pro — {price}">Unlock Pro</button>
دکمههایی که React بعداً سوار میکند خودکار پیدا میشوند و سوار شدن دوباره بیخطر است. در Next.js بهصورت خروجی ایستا بسازید (output: 'export').
کد را به برچسبها ترجیح میدهید؟ یک خط همین کار را میکند:
AppMintIAP.sell('pro_unlock', '#buy-btn');
آرگومان سوم اختیاری: {label:'Unlock Pro - {price}', owned:'✓ Unlocked', show:'#thanks', hide:'.upsell'}. برای هر محصول یک بار صدا بزنید.
فقط اگر میخواهید خود React قیمت را بکشد - در غیر این صورت این را رد کنید:
const [iap, setIap] = useState({ available: false, products: {} });
useEffect(() => {
if (typeof window === 'undefined' || !window.AppMintIAP) return;
window.AppMintIAP.start({ plans: [{ id: 'pro_unlock' }] });
return window.AppMintIAP.onChange(setIap);
}, []);
const pro = iap.products?.pro_unlock; // همیشه optional-chain
if (!iap.available) return null; // بیرون از اپلیکیشن چیزی برای خرید نیست
if (pro?.owned) return <p>Thanks!</p>;
return <button onClick={() => window.AppMintIAP.buy('pro_unlock')}>
Unlock Pro {pro?.price}
</button>;
دو عادت جلوی خراب شدن این را میگیرند:
- مالکیت را برای هر محصول جداگانه بخوانید - iap.products?.<id>?.owned. iap.owned در سطح بالا یعنی «چیزی مالکیت شده» و روزی که محصول دوم بفروشید معنایش عوض میشود.
- کل مسیر را با optional chaining بنویسید تا فیلدی که انتظارش را نداشتید هرگز هنگام رندر React خطا ندهد.
onChange فوراً و در هر تغییر {available, adFree, owned, ownedIds, products} را به شما میدهد و یک تابع لغو اشتراک برمیگرداند - پس خریدار بازگشتی از همان رندر اول قفلش باز است. در Next.js 'use client' را بهعنوان خط ۱ اضافه کنید و با output: 'export' بسازید.
همچنین: AppMintIAP.available، .isOwned(id)، .owned()، .buy(id)، .state()، .refresh(). برچسبهای data-iap-buy در JSX هم کار میکنند، و دکمههایی که React بعداً سوار میکند خودکار شناسایی میشوند.
ترجیح میدهید خودتان مستقیم با bridge خام کار کنید؟ همهٔ موارد بالا روی اینها ساخته شدهاند:
- iap.isOwned(id) - درست/نادرست فوری، آفلاین کار میکند
- iap.getOwnedProducts() - آرایهٔ JSON از همهٔ شناسههای مالکیتشده
- iap.getProducts(reqId, idsJson) - قیمتهای زنده؛ نتیجه بهصورت رویداد
'appmint:products'میرسد {requestId, products:[{productId, title, price, currency, type, owned}]} - iap.purchase(id) - محصولات یکباره و اشتراکها (بهصورت خودکار تشخیص داده میشود)
- رویدادها:
'appmint:purchase'{productId} (فقط اینجا اعطا کنید - زدن روی چیزی که از قبل مالک آن هستید دوباره آن را فعال میکند)،'appmint:owned-changed'{products} (بازیابی هنگام راهاندازی تمام شد، یا بازپرداخت چیزی را برداشت - مالکیت را دوباره بخوانید)،'appmint:purchase-failed'{reason: disabled | not_found | billing_error | pending}
فقط یک ارتقای «حذف تبلیغات» میفروشید؟ بهجای آن از جفت داخلی سادهتر استفاده کنید - در مرحلهٔ ۳ ویزارد «Remove Ads (IAP)» را فعال کنید:
var iap = window.WebToApk;
if (iap && !iap.isPremium()) {
showRemoveAdsButton(function onTap() {
iap.startRemoveAdsPurchase();
});
}
window.addEventListener('appmint:premium', function (e) {
if (e.detail.premium) hideRemoveAdsButton();
});Remove Ads (IAP)
تنظیم Google Play Billing
1ساخت محصول مدیریتشده (Managed Product)#
در داشبورد Google Play Console خود:
- به «Monetization» > «In-app products» بروید
- روی «Create product» بزنید
- یک Product ID وارد کنید (مثلاً «remove_ads_premium»)
- یک Name (مثلاً «Remove All Ads») و Description تعیین کنید
- قیمت را تعیین کنید و روی «Save» بزنید
- روی «Activate» بزنید تا برای آزمایش در دسترس شود
حذف تبلیغات را بهصورت اشتراک میفروشید؟ آن را زیر «Monetization» > «Subscriptions» با base plan تمدیدشونده بسازید، و هم base plan و هم اشتراک را فعال کنید. بدونتبلیغ بودن تا وقتی اشتراک فعال است ادامه دارد، و وقتی منقضی شود تبلیغات خودکار برمیگردند.
2کلید مجوز خود را بگیرید#
Google Play برای تأیید امن خریدها به یک کلید عمومی RSA نیاز دارد.
در Play Console:
- به «Monetization» > «Monetization setup» بروید
- زیر «Licensing»، «Base64-encoded RSA public key» را کپی کنید
- ⚠️ مهم: بعداً باید این را در Generator جایگذاری کنید.
Example Key Format:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...3تنظیم در WebToApk Generator#
در اپلیکیشن generator:
- کلید «Remove Ads (IAP)» را در مرحلهٔ ۳ فعال کنید
- دقیقاً همان «Product ID» را که در مرحلهٔ ۱ ساختید وارد کنید
- «Base64 License Key» خود را از مرحلهٔ ۲ جایگذاری کنید
- APK/AAB خود را تولید کنید
بیش از یک طرح بدون تبلیغ ارائه میدهید (مثلاً اشتراک ماهانه و باز کردن دائمی یکباره)؟ هر Product ID را در همین یک فیلد بگذارید و با کاما جدا کنید - مالکیت هر یک از آنها تبلیغات را حذف میکند. اولین شناسهٔ فهرست همان است که گزینهٔ منوی داخلی «Remove Ads» میخرد، پس طرح پیشفرض خود را اول بگذارید.
Product ID: remove_ads_premium
Two plans:
remove_ads_monthly,remove_ads_lifetime4تنظیم License Testers#
برای آزمایش خرید بدون خرج کردن پول واقعی:
- در Play Console به «Setup» > «License testing» بروید
- آدرس Gmail خود را به فهرست «License testers» اضافه کنید
- مطمئن شوید «License response» روی «RESPOND_NORMALLY» تنظیم شده است
5در Internal یا Closed Track آزمایش کنید#
خریدهای درونبرنامهای فقط وقتی کار میکنند که اپلیکیشن از طریق Play Store نصب شده باشد (مسیرهای Testing):
- AAB تولیدشدهٔ خود را در مسیر «Internal Testing» یا «Closed Testing» بارگذاری کنید
- لینک join آزمایشکنندگان را با حسابهای آزمایشی خود به اشتراک بگذارید
- اپلیکیشن را از Play Store دانلود کنید
- برای آزمایش جریان، دکمهٔ «Remove Ads» را در اپلیکیشن خود بزنید!
Note: Purchases will show a 'Test instrument, always approves' message for license testers.دستورهای کد
مراحل بالا مبانی را پوشش میدهند - چهار برچسب، یک صفحهٔ ساده کامل، یکخطی React. این دستورها آنچه را آنها پوشش نمیدهند پوشش میدهند: چند طرح که جایگزین یکدیگرند، اشتراکهایی که منقضی میشوند، hookهای فریمورک، bridge خام بهطور کامل، و جدولهای مرجع.
تنها یک قاعده#
خرید فقط وقتی اعطا میشود که Google آن را تأیید کند. نه وقتی دکمه کلیک میشود، نه وقتی صفحهٔ پرداخت باز میشود، نه وقتی کاربر به صفحهٔ شما برمیگردد. هر چیز زیر راه دیگری برای بیان همین یک قاعده است.
runtime از قبل آن را اجرا میکند: AppMintIAP مالکیت را از bridge دوباره میخواند، هنگام راهاندازی، در هر خرید، در هر بازپرداخت و هر بار که اپلیکیشن دوباره باز میشود. مالکیت از نصب دوباره، گوشی جدید و آفلاین بودن جان سالم به در میبرد، چون در حساب Google خریدار است، نه در صفحهٔ شما.
هرگز در رویدادگردان کلیک قفل را باز نکنید، و هرگز حق دسترسی را فقط در localStorage نگه ندارید. پاک شدن حافظهٔ مرورگر نباید باعث شود مشتری پولی چیزی را که خریده از دست بدهد - و کپی کردن یک مقدار localStorage نباید آن را رایگان بدهد.
دستور ۱ - جدول قیمت با قیمتهای زنده#
سه طرح که خرید هر یک جایگزین بقیه میشود. group است که آنها را جایگزین یکدیگر میکند: بدون آن، محصولات نامرتبطاند و خرید طرح دائمی دکمهٔ ماهانه را همانجا باقی میگذارد.
<div class="plans" data-iap-container>
<button data-iap-buy="pass_monthly" data-iap-group="pass"
data-iap-label="Monthly — {price}" data-iap-owned="Active">Monthly</button>
<button data-iap-buy="pass_yearly" data-iap-group="pass"
data-iap-label="Yearly — {price}" data-iap-owned="Active">Yearly</button>
<button data-iap-buy="pass_lifetime" data-iap-group="pass"
data-iap-label="Lifetime — {price}" data-iap-owned="Owned">Lifetime</button>
</div>
<p data-iap-show="pass">You are a member. Thank you.</p>
<p data-iap-hide="pass">Members get every level and no ads.</p>شناسهٔ محصولی که Google نمیشناسد - هنوز ساخته نشده، فعال نشده، یا اپلیکیشن از Play Store نصب نشده - بدون قیمت میماند. دکمهٔ آن عمداً قابل زدن میماند: زدن آن دلیل واقعی را گزارش میدهد بهجای اینکه کاری نکند.
دستور ۲ - React#
onChange فوراً با وضعیت فعلی فعال میشود و دوباره در هر تغییر، و تابع لغو اشتراک خودش را برمیگرداند - پس دقیقاً همان شکلی است که یک useEffect میخواهد.
import { useEffect, useState } from 'react';
/** Purchase state for the whole app. Safe in a browser too: available is false there. */
export function useIap() {
const [iap, setIap] = useState({ available: false, ownedIds: [], products: {}, adFree: false });
useEffect(() => {
if (!window.AppMintIAP) return; // running in a browser, or IAP not enabled
return window.AppMintIAP.onChange(setIap); // returns the unsubscribe fn
}, []);
return {
...iap,
owns: (id) => iap.ownedIds.includes(id),
priceOf: (id) => iap.products[id]?.price ?? null,
buy: (id) => window.AppMintIAP?.buy(id)
};
}import { useIap } from './hooks/useIap';
export function ProGate({ children }) {
const { available, owns, priceOf, buy } = useIap();
if (owns('pro_unlock')) return children;
return (
<div className="upsell">
<h2>Pro</h2>
<p>Unlimited projects, no watermark.</p>
<button onClick={() => buy('pro_unlock')} disabled={!available}>
{available ? `Unlock Pro${priceOf('pro_unlock') ? ` — ${priceOf('pro_unlock')}` : ''}` : 'Not available here'}
</button>
</div>
);
}دکمه buy() را صدا میزند؛ چیزی را باز نمیکند. باز شدن به این دلیل رخ میدهد که Google تأیید میکند، onChange فعال میشود، owns('pro_unlock') درست (true) میشود و کامپوننت دوباره رندر میشود. این همان قاعده است که در React بیان شده.
دستور ۳ - Vue#
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
const iap = ref({ available: false, ownedIds: [], products: {} });
let stop;
onMounted(() => { stop = window.AppMintIAP?.onChange(s => (iap.value = s)); });
onUnmounted(() => stop && stop());
const isPro = computed(() => iap.value.ownedIds.includes('pro_unlock'));
const price = computed(() => iap.value.products.pro_unlock?.price ?? '');
</script>
<template>
<slot v-if="isPro" />
<button v-else :disabled="!iap.available" @click="AppMintIAP.buy('pro_unlock')">
Unlock Pro {{ price }}
</button>
</template>دستور ۴ - اشتراکها#
اشتراک دقیقاً مثل محصول یکباره خریده میشود؛ تفاوت در این است که میتواند از بین برود. type میگوید محصول از کدام نوع است و period بازهٔ صورتحساب را بهصورت مدت ISO-8601 میدهد - P1M ماهانه، P1Y سالانه، P1W هفتگی.
AppMintIAP.onChange(state => {
const pass = state.products.season_pass; // undefined until Google answers
const active = state.ownedIds.includes('season_pass');
document.body.classList.toggle('has-pass', active);
if (pass && !active) {
const every = { P1W: 'week', P1M: 'month', P1Y: 'year' }[pass.period] || 'period';
label.textContent = `Season pass — ${pass.price} / ${every}`;
}
});
// The pass lapsed (not renewed, or refunded): the same handler runs with it gone.
// Nothing to write — the class comes off and the paid content hides itself.نیازی به polling و تاریخ انقضا ندارید. Play مجموعهٔ مالکیتها را در هر راهاندازی بازیابی میکند، پس یک اشتراک منقضیشده بهسادگی دیگر در ظاهر نمیشود ownedIds.
هم اشتراک و هم base plan آن را در Play Console فعال کنید. اشتراکی که base plan غیرفعال دارد هیچ جزئیاتی برنمیگرداند و دکمه «not available on Google Play yet» گزارش میدهد.
دستور ۵ - bridge خام، بدون کمکی#
همهٔ موارد بالا روی شش متد و پنج رویداد ساخته شدهاند. اگر میخواهید رابط کاربری را کاملاً خودتان بسازید، مستقیم از آنها استفاده کنید.
const bridge = window.WebToApk;
const enabled = !!(bridge && bridge.purchase); // false in a browser
// --- what does Google charge for these, in this buyer's currency? ---
const reqId = 'prices-' + Date.now();
window.addEventListener('appmint:products', function onProducts(e) {
if (e.detail.requestId !== reqId) return; // not our answer
window.removeEventListener('appmint:products', onProducts);
for (const p of e.detail.products) {
// {productId, type:'inapp'|'subs', title, description, price, currency, period?, owned}
render(p);
}
});
bridge.getProducts(reqId, JSON.stringify(['pro_unlock', 'season_pass']));
// --- what does this buyer already own? instant, and works offline ---
const owned = JSON.parse(bridge.getOwnedProducts() || '[]');
const isPro = bridge.isOwned('pro_unlock');
// --- buy ---
buyButton.onclick = () => bridge.purchase('pro_unlock');
// --- the ONLY place a purchase is ever granted ---
window.addEventListener('appmint:purchase', e => {
if (e.detail.productId === 'pro_unlock' && e.detail.owned) unlockPro();
});
// --- and the only place failures show up ---
window.addEventListener('appmint:purchase-failed', e => {
toast({
not_found: 'This item is not available on Google Play yet.',
billing_error: 'Google Play could not start the payment. Please try again.',
pending: 'Payment pending — this unlocks automatically once Google confirms.',
disabled: 'In-app purchases are not enabled in this app.'
}[e.detail.reason] || 'Purchase could not be completed. Please try again.');
});
// --- the launch-time restore finished, or a refund took something away ---
window.addEventListener('appmint:owned-changed', () => refreshEverything());appmint:purchase عمداً دوباره برای چیزی که از قبل مالک آن هستید فعال میشود. unlockPro() را طوری بنویسید که دوبار اجرا شدنش بیضرر باشد و لازم نباشد پیگیری کنید که قبلاً اجرا شده یا نه.
دستور ۶ - بیرون از اپلیکیشن صادقانه تنزل دهید#
همان صفحه معمولاً باید در مرورگر عادی هم باز شود - هنگام توسعه، یا چون آن را روی وب هم منتشر میکنید. آنجا window.WebToApk وجود ندارد و هیچ خریدی ممکن نیست. این را بگویید، بهجای نشان دادن دکمهای که کاری نمیکند.
const inApp = !!(window.AppMintIAP && window.AppMintIAP.available);
if (!inApp) {
buyButton.disabled = true;
buyButton.textContent = 'Available in the Android app';
// and let people get it:
storeLink.hidden = false;
}مرجع#
همهٔ آنچه صفحه میتواند صدا بزند، به آن گوش دهد یا علامتگذاری کند.
| فراخوانی | کاری که میکند |
|---|---|
AppMintIAP.available | در مرورگر نادرست (false) است، یا وقتی خریدهای درونبرنامهای برای این ساخت روشن نشدهاند. قبل از نشان دادن هر چیز برای فروش آن را بررسی کنید. |
AppMintIAP.sell(id, button, opts) | یک محصول را به یک دکمه وصل میکند. opts: label، owned، group، show، hide، labelEl. برای هر محصول یک بار صدا بزنید. |
AppMintIAP.start(config) | چند مورد را یکجا وصل کنید: {plans, show, hide, container, toast, hideOtherPlans}. وقتی میخواهید تابع toast خودتان را داشته باشید، یا میخواهید بقیهٔ طرحهای یک گروه پس از خرید یکی قابل مشاهده بمانند از آن استفاده کنید (hideOtherPlans: false). |
AppMintIAP.onChange(fn) | فوراً با وضعیت فعلی فعال میشود، سپس در هر تغییر. یک تابع لغو اشتراک برمیگرداند. |
AppMintIAP.state() | همان شیء، یکبار: {available, adFree, owned, ownedIds, products}. |
AppMintIAP.isOwned(id) | درست/نادرست، فوری، آفلاین. |
AppMintIAP.owned() | آرایهای از همهٔ شناسههای محصولِ مالکیتشده. |
AppMintIAP.buy(id) | صفحهٔ خرید Google را باز میکند. بهتنهایی چیزی اعطا نمیکند. |
AppMintIAP.refresh() | دکمههای data-iap-buy را که از آخرین بررسی اضافه شدهاند دوباره اسکن میکند. بهندرت لازم است - runtime خودش DOM را زیر نظر دارد. |
| فراخوانی | برمیگرداند |
|---|---|
purchase(productId) | هیچ. صفحهٔ پرداخت را باز میکند؛ نتیجه بهصورت رویداد میرسد. |
getProducts(requestId, idsJson) | هیچ. پاسخ بهصورت appmint:products میرسد و همان را حمل میکند requestId. |
getOwnedProducts() | آرایهٔ JSON از شناسههای محصولِ مالکیتشده، بهصورت رشته. |
isOwned(productId) | بولی - یک محصول یکباره مالکیت شده، یا یک اشتراک که در حال حاضر فعال است. |
isPremium() | بولی - بهطور خاص حق دسترسی «حذف تبلیغات». |
startRemoveAdsPurchase() | هیچ. صفحهٔ پرداخت را برای اولین شناسه در فیلد «Remove Ads» ویزارد باز میکند؛ مالکیت هر شناسهای که آنجا فهرست شده تبلیغات را حذف میکند. نتیجه بهصورت میرسد appmint:premium. |
| رویداد | جزئیات | زمان |
|---|---|---|
appmint:purchase | {productId, owned} | Google تأیید کرد. تنها جایی که باید قفل را باز کنید. برای چیزی که از قبل مالک آن هستید دوباره فعال میشود. |
appmint:purchase-failed | {productId, reason} | صفحهٔ پرداخت هرگز باز نشد، جریان خطا داد، یا پرداخت در انتظار است. برای لغو ساده هرگز فعال نمیشود. |
appmint:products | {requestId, products[]} | پاسخ به یک فراخوانی getProducts(). شناسههایی که Play نمیشناسد بهسادگی وجود ندارند. |
appmint:premium | {premium} | حق دسترسی بدون تبلیغ تغییر کرد - خرید، بازیابی هنگام راهاندازی، بازپرداخت یا انقضا. |
appmint:owned-changed | {products[]} | بازیابی هنگام راهاندازی تمام شد، یا بازپرداخت چیزی را برداشت. همهٔ شناسههای مالکیتشده را حمل میکند. |
| ویژگی | روشن | کاری که میکند |
|---|---|---|
data-iap-buy="id" | دکمه | باعث میشود آن محصول را بخرد. |
data-iap-label="… {price}" | دکمه | قالب برچسب. {price} به قیمت زندهٔ محلیشدهٔ Google تبدیل میشود. |
data-iap-owned="…" | دکمه | برچسبی که پس از مالکیت محصول نشان داده میشود. |
data-iap-group="name" | دکمه | طرحها را جایگزین یکدیگر علامت میزند. |
data-iap-show="id|group" | هر چیزی | فقط وقتی نشان داده میشود که آن محصول مالکیت شده باشد. |
data-iap-hide="id|group" | هر چیزی | وقتی آن محصول مالکیت شد پنهان میشود. |
data-iap-text | داخل یک دکمه | عنصری که برچسب در متن آن قرار میگیرد، وقتی دکمه چیزی بیش از متن در خود دارد. |
data-iap-container | پوشش | فقط داخل اپلیکیشن نشان داده میشود؛ در مرورگر که چیزی برای خرید نیست پنهان است. کل پیشنهاد را در آن بپیچید. |
data-iap-state="owned" | توسط runtime تنظیم میشود | روی دکمهٔ یک محصول مالکیتشده، تا بتوانید آن را استایل بدهید. |
| دلیل | واقعاً چه اتفاقی افتاد |
|---|---|
not_found | Google این شناسهٔ محصول را نمیشناسد - ساخته نشده، فعال نشده، یا اپلیکیشن از Play Store نصب نشده است. |
billing_error | Play نتوانست پرداخت را شروع کند. معمولاً موقتی است؛ خریدار باید دوباره امتحان کند. |
pending | یک روش پرداخت کند (نقدی، حواله بانکی). وقتی Google تأیید کند خودبهخود باز میشود - این را شکست حساب نکنید. |
disabled | خریدهای درونبرنامهای برای این ساخت روشن نشده بودند. |
unknown | هر چیز دیگر. |
پیش از انتشار#
- هر شناسهٔ محصول در کد شما در Play Console وجود دارد و فعال است - برای اشتراک، base plan هم.
- ساخت همان شناسهها را دارد، بهعلاوهٔ کلید مجوز Base64 RSA از Monetization setup.
- آدرس Gmail شما در Setup ← License testing است و پاسخ روی RESPOND_NORMALLY تنظیم شده است.
- AAB را در Internal یا Closed testing بارگذاری کردید و آن را از لینک Play Store نصب کردید - APK نصبشده بهصورت sideload هرگز نمیتواند چیزی بخرد.
- هر محصول را بهعنوان آزمایشکنندهٔ مجوز خریدید و «Test instrument, always approves» را دیدید.
- حذف و دوباره نصب کردید، و خرید خودبهخود برگشت.
- شبکه را قطع کردید و محتوای پولی همچنان باز است.
- یک خرید آزمایشی را در Play Console بازپرداخت کردید و دیدید اپلیکیشن دوباره قفل شد.
اشتباهاتی که پول خرج دارند#
- باز کردن قفل در رویدادگردان کلیک. هر کس صفحهٔ پرداخت را باز کند و منصرف شود، محصول را رایگان میگیرد.
- ذخیرهٔ حق دسترسی فقط در
localStorage. پاک کردن دادههای سایت چیزی را که مشتری پرداخت کرده از او میگیرد؛ کپی کردن یک مقدار آن را رایگان میدهد. - ثابت نوشتن قیمت در HTML. در هر کشور دیگری اشتباه خواهد بود و روزی که تغییرش دهید قدیمی میشود. از
{price}یا استفاده کنیدproducts[id].price. - فروش کالای فیزیکی یا خدمات دنیای واقعی از طریق Play billing. Google دقیقاً عکس این را میخواهد: آنها باید از یک پردازشگر پرداخت معمولی استفاده کنند. راهنمای Stripe برای همین است.
- آزمایش روی APK نصبشده بهصورت sideload. خریدهای درونبرنامهای فقط وقتی کار میکنند که اپلیکیشن از Play Store نصب شده باشد. هیچ چیزی که بنویسید آن را تغییر نمیدهد.
- در نظر گرفتن
pendingبهعنوان شکست. خریدار با روش کند پرداخت کرده است؛ وقتی Google تأیید کند خودبهخود باز میشود.