أنشئ APK مجانًا

تحقيق الدخل

بِع منتجات داخل التطبيق (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، وارفعه في المعالج، واتبع الدليل. إنه أسرع طريقة لرؤية عملية شراء حقيقية تعمل من البداية إلى النهاية قبل أن تلمس تطبيقك.

في التطبيق، يحفظ زر هذه الخطوة هذه الملفات على هاتفك. أما هنا فهي تنزيلات مباشرة.

AppMint-InApp-Purchases-Guide.pdf PDF · 257 KB iap-demo-index.html HTML · 10 KB

2ما هذا؟ (بكلمات بسيطة)#

يستطيع تطبيقك أن يبيع أشياء، مثل ترقية Pro أو مستويات إضافية أو اشتراك، وGoogle يجمع المال ويدفعه لك كل شهر.

سنبني مثالًا واحدًا معًا، على امتداد هذا الدليل:

A game that sells two things
  • 'Levels 11-20'      ₹99  paid once, kept forever
  • 'Remove ads'        ₹49  every month

ستنتهي بإضافة أربعة أسطر إلى صفحتك. هذا هو العمل كله، فلا يوجد كود شراء لتكتبه، وحتى لو لم تفعل هذا من قبل فستنجح.

لا تحتاج إلى معرفة البرمجة سوى النسخ واللصق، ولا تحتاج إلى خادم أو قاعدة بيانات أو حساب لدى شركة دفع. Google Play يتولى الأجزاء الصعبة:

  • يعرض شاشة الدفع (بطاقة / UPI / الفوترة عبر شركة الاتصالات)
  • يحوّل سعرك إلى عملة كل بلد
  • يتذكر من اشترى ماذا، حتى بعد إعادة التثبيت
  • يرسل الأموال إلى حسابك المصرفي

AppMint يأخذ 0%، فالبيع يذهب مباشرة إلى حساب Google Play الخاص بك.

يعمل بالطريقة نفسها مع الموقع إلى APK وZIP إلى APK والتطبيقات المبنية بالذكاء الاصطناعي.

3ما تحتاجه قبل أن تبدأ#

أربعة أشياء فقط:

  1. موقعك أو تطبيق HTML/ZIP (الشيء الذي تحوّله إلى APK).
  2. شيء تبيعه: قرر ماذا سيحصل عليه المشترون (مثل «النسخة Pro»: بلا حدود ومحتوى إضافي).
  3. حساب مطوّر Google Play - رسوم لمرة واحدة قدرها 25 دولارًا على play.google.com/console. تحتاجه لنشر أي تطبيق على متجر Play، وليس المدفوعة فقط.
  4. حوالي ساعة واحدة في المجمل، موزعة على الخطوات أدناه.

مهم أن تعرف مسبقًا: المشتريات داخل التطبيق لا تعمل إلا عندما يكون تطبيقك مثبّتًا من متجر Play. هذه قاعدة من Google للجميع، وليست حدًا من AppMint.

4الخطوة 1 - اخترع معرّف منتجك#

معرّف المنتج مجرد اسم قصير تختلقه للشيء الذي تبيعه. ستكتب هذا الاسم نفسه في مكانين لاحقًا (صفحتك وPlay Console)، فاجعله بسيطًا ودوّنه.

القواعد: أحرف صغيرة وأرقام وشرطات سفلية فقط. يجب أن يبدأ بحرف أو رقم.

انسخ واحدًا من هذه:

  • pro_unlock - ترقية «النسخة Pro» (اختيار افتراضي جيد، والكود أدناه يستخدمه بالفعل)
  • remove_ads - ترقية بدون إعلانات
  • level_pack_2 - حزمة محتوى إضافية
  • coins_100 - حزمة عملة اللعبة
  • premium_monthly - اشتراك شهري

السعر ليس جزءًا من المعرّف، ستحدد السعر في الخطوة 5 ويمكنك تغييره في أي وقت دون لمس الكود.

5الخطوة 2 - ضع تسمية على زرك (بلا كود تكتبه)#

لا تكتب أي كود شراء. تطبيقك يحمل محرك الشراء بالفعل، وأنت فقط تضع تسميات على HTML الخاص بك ليعرف ما يبيعه.

انسخ الكتلة التالية إلى صفحتك وغيّر فقط:

(1) pro_unlock ← معرّف منتجك، في data-iap-buy
(2) الكلمات على الزر وداخل القسم المدفوع

هذا كل شيء. يعرض الزر عندها السعر الحقيقي من 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".

فقط المعرّفات التي تكتبها في «إزالة الإعلانات (IAP) ← معرّف المنتج» توقف الإعلانات. كل ما تبيعه غير ذلك يفتح فقط ما تفعله صفحتك به، وتستمر الإعلانات.

خطتان للشيء نفسه (شهري مقابل مدى الحياة)؟ أعطِ الزرين data-iap-group="adfree" نفسه، فيخفي شراء أحدهما الآخر ولا يدفع أحد مرتين. بدون مجموعة لا يُخفى شيء، وهذا ما تحتاجه المنتجات غير المترابطة.

<!-- (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، أما كود صفحتك فله الشكل نفسه في كل مرة.

  1. إزالة الإعلانات، بالدفع مرة واحدة (للأبد)

أنشئه تحت: المنتجات داخل التطبيق
اكتب المعرّف أيضًا في: إزالة الإعلانات (IAP) ← معرّف المنتج

  1. إزالة الإعلانات، شهريًا

أنشئه تحت: الاشتراكات
اكتب المعرّف أيضًا في: إزالة الإعلانات (IAP) ← معرّف المنتج

  1. فتح المستويات / المحتوى، بالدفع مرة واحدة

أنشئه تحت: المنتجات داخل التطبيق
لا تكتبه في خانة إزالة الإعلانات، وإلا ستستمر إعلاناتك بالعمل.

  1. فتح المستويات / المحتوى، شهريًا

أنشئه تحت: الاشتراكات
لا تكتبه في خانة إزالة الإعلانات، وإلا ستستمر إعلاناتك بالعمل.

يمكنك بيع الأربعة جميعًا في التطبيق نفسه، وبأي عدد من المنتجات تريد. شراء أحدها لا يؤثر أبدًا على الباقي.

<!-- 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أي المشتريات تزيل الإعلانات؟#

هذا هو الشيء الوحيد الذي يخطئ فيه الناس، لذا يستحق 30 ثانية.

إعلاناتك ليست جزءًا من صفحة الويب. إنها إعلانات Android حقيقية موضوعة فوقها، لذلك لا يمكن لأي شيء في صفحتك إيقافها. شيء واحد فقط يستطيع ذلك:

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

لا تبيع إزالة الإعلانات إطلاقًا؟ اترك ذلك المفتاح مُعطّلًا تمامًا واستخدم فقط «💰 بيع منتجات داخل التطبيق».

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 عادية#

هذا تطبيق مثالنا كاملًا: يبيع «المستويات 11-20» مرة واحدة، و«إزالة الإعلانات» شهريًا. انسخه، وغيّر المعرّفين والكلمات، وانتهيت.

اقرأ ما تفعله كل تسمية:

  • 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' في السطر 1 من الملف، وإلا فشل البناء.
  • لا تلمس window.AppMintIAP إلا داخل useEffect، فهو في جسم المكوّن يعمل على الخادم حيث لا يوجد 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الخطوة 3 - شغّل المشتريات في المعالج#

⚠️ لا تتجاوز هذا. إن فعلت، ستظهر أزرارك لكن كل ضغطة ستجيب «المشتريات داخل التطبيق غير مفعّلة في هذا التطبيق.» ولن تحصل على أي مال.

المعالج (STEPPER) هو شاشات الخطوة 1 ← الخطوة 2 ← الخطوة 3 التي تستخدمها لبناء تطبيقك. المشتريات موجودة في الخطوة 3 من المعالج.

افعل هذا بالترتيب:

  1. افتح المعالج واذهب إلى الخطوة 3 (الشاشة التي فيها الإعلانات والإشعارات والأذونات).
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الخطوة 4 - ضع تطبيقك على Google Play#

في Play Console (الموقع الذي يدير فيه المطورون تطبيقاتهم):

  1. «إنشاء تطبيق» ← املأ الاسم والأسئلة الأساسية.
  2. في القائمة اليسرى: «الاختبار» ← «الاختبار الداخلي» ← «إنشاء إصدار جديد» ← ارفع ملف AAB ← «حفظ» ثم اطرحه.
  3. ما زلت في «الاختبار الداخلي» ← تبويب «المختبرون» ← أضف حساب Gmail الخاص بك ← انسخ رابط «الانضمام» لاستخدامه لاحقًا.

الاختبار الداخلي منطقة خاصة، ولا يستطيع أحد رؤية تطبيقك بعد. وُجد لتجرّب أنت المشتريات قبل النشر للعامة.

13الخطوة 5 - أنشئ منتجك وحدّد السعر#

الآن أخبر Google بما تبيعه. القائمة التي تستخدمها تعتمد على ما إذا كانوا يدفعون مرة واحدة أو كل شهر.

الدفع مرة واحدة (مستويات، Pro، عملات، إزالة الإعلانات للأبد)

  1. Play Console ← تطبيقك ← «تحقيق الدخل» ← «المنتجات» ← «المنتجات داخل التطبيق».
  2. «إنشاء منتج».
  3. معرّف المنتج: اكتب بالضبط المعرّف من صفحتك - level_pack_2. الإملاء نفسه وحالة الأحرف نفسها. لا يسمح لك Google أبدًا بتغييره أو إعادة استخدامه.
  4. الاسم: ما يراه المشترون، مثل «المستويات 11-20». الوصف: سطر واحد.
  5. حدّد سعرك بعملتك الخاصة، وGoogle يحوّله لكل بلد.
  6. اضغط «تفعيل».

كل شهر (إزالة الإعلانات شهريًا، اشتراك كل المستويات)

  1. Play Console ← «تحقيق الدخل» ← «المنتجات» ← «الاشتراكات».
  2. «إنشاء اشتراك». معرّف المنتج: remove_ads_monthly.
  3. احفظ، ثم أضف خطة أساسية: النوع «تجديد تلقائي»، فترة الفوترة «شهريًا»، وسعرك.
  4. اضغط «تفعيل» على الخطة الأساسية وعلى الاشتراك نفسه. إذا فاتك أحدهما فلن يتمكن تطبيقك من رؤيته.

كرر ذلك لكل معرّف استخدمته. كود صفحتك لا يتغير، فالتطبيق يسأل Google عن نوع كل معرّف.

تريد شهريًا وسنويًا معًا؟ أنشئ اشتراكين منفصلين (all_levels_monthly وall_levels_yearly) بدل خطتين أساسيتين داخل اشتراك واحد. الاشتراك الواحد ذو الخطط الأساسية المتعددة يعرض دائمًا الأولى، دون وسيلة للاختيار.

غيّر أي سعر لاحقًا في Play Console، وسيعرض تطبيقك السعر الجديد من تلقاء نفسه دون إعادة بناء.

14الخطوة 6 - اختبره (لن يُخصم منك شيء)#

  1. Play Console ← القائمة اليسرى «الإعداد» ← «اختبار الترخيص» ← أضف Gmail الخاص بك إلى قائمة المختبرين.
  2. على هاتفك: افتح رابط الانضمام للاختبار الداخلي من الخطوة 4، واقبل، وثبّت تطبيقك من متجر Play.
  3. افتح التطبيق. يظهر زر الشراء بالسعر الحقيقي.
  4. اضغط عليه، فتفتح شاشة الدفع من Google وتقول «بطاقة اختبار، توافق دائمًا». اشترِ المنتج. يظهر المحتوى المدفوع، ولا تدفع شيئًا.
  5. فحص إضافي: ألغِ تثبيت التطبيق وأعد تثبيته، فيُفتح المحتوى المدفوع من تلقاء نفسه عند التشغيل. هذا هو Google يستعيد الشراء.

⚠️ إذا ثبّتَّ ملف APK مباشرة (sideload) بدل متجر Play، فلن يظهر زر الشراء. هذا طبيعي، اختبر دائمًا عبر رابط متجر Play.

15الخطوة 7 - انشر واستلم أرباحك#

  1. عندما يبدو الاختبار جيدًا: Play Console ← «الإنتاج» ← أنشئ إصدارًا بملف AAB نفسه ← اطرحه. (التطبيقات الجديدة تمر بمراجعة Google، وعادة تستغرق بضعة أيام.)
  2. يشتري المستخدمون داخل تطبيقك ← Google يجمع المال.
  3. يدفع Google لحسابك المصرفي شهريًا (Play Console ← «ملف المدفوعات»). يحتفظ Google برسوم خدمته القياسية (15% لمعظم المطورين). AppMint لا يحتفظ بشيء.

لإضافة منتجات لاحقًا: أضف المعرّف الجديد في كود صفحتك، وأعد البناء مرة واحدة، وأنشئ المعرّف في Play Console. تغييرات الأسعار لا تحتاج إلى إعادة بناء أبدًا.

16إذا لم يعمل شيء#

يخبرك تطبيقك بما حدث بدل أن يفشل بصمت. ابحث عن الرسالة التي تراها:

«المشتريات داخل التطبيق غير مفعّلة في هذا التطبيق.»
← كان خيار «💰 بيع منتجات داخل التطبيق» مُعطّلًا في الخطوة 3 من المعالج وقت البناء. فعّله وابنِ مرة أخرى.

«هذا العنصر غير متاح على Google Play بعد.»
← Google لا يتعرف على هذا المعرّف. تحقق منه حرفًا حرفًا مقابل Play Console، وتأكد أن المنتج نشط (للاشتراك يجب تفعيل الخطة الأساسية والاشتراك معًا)، وتأكد أنك ثبّتَّ التطبيق من رابط مختبري Play.

«تعذّر على Google Play بدء الدفع.»
← لم يُثبَّت التطبيق من متجر Play، أو وُقّع بمفتاح مختلف عن الذي رفعته.

«الدفع معلّق - سيُفتح تلقائيًا بمجرد أن يؤكد Google.»
← لا شيء تفعله. طرق الدفع البطيئة (قسائم نقدية، بعض UPI) تستغرق بضع دقائق، ويصل الفتح من تلقاء نفسه، حتى بعد إعادة تشغيل التطبيق.

لا رسالة إطلاقًا، ولا يحدث شيء عند الضغط
← غالبًا خطأ إملائي في data-iap-buy، أو أن التطبيق غير مثبّت من Play. كل الأسباب الأخرى تعرض إحدى الرسائل أعلاه.

تم الدفع لكن الإعلانات ما زالت موجودة
← معرّف المنتج هذا غير موجود في الخطوة 3 من المعالج ← «إزالة الإعلانات (IAP)» ← «معرّف المنتج في متجر Play». أضفه (افصل بين عدة معرّفات بفاصلة) وابنِ مرة أخرى.

زر الشراء لا يظهر أبدًا في التطبيق
← كان خيار «💰 بيع منتجات داخل التطبيق» مُعطّلًا وقت البناء. في متصفح الويب العادي تكون الأزرار مخفية عن قصد، فلا يوجد ما يُشترى خارج التطبيق.

لم يحدث شيء بعد إغلاق شاشة الدفع من 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; // استخدم دائمًا التسلسل الاختياري

if (!iap.available) return null; // لا شيء يُشترى خارج التطبيق
if (pro?.owned) return <p>شكرًا!</p>;
return <button onClick={() => window.AppMintIAP.buy('pro_unlock')}>

       Unlock Pro {pro?.price}
     </button>;

عادتان تمنعان هذا من الانكسار:

  1. اقرأ الملكية لكل منتج - iap.products?.<id>?.owned. القيمة iap.owned في المستوى الأعلى تعني «أي شيء مملوك» ويتغير معناها يوم تبيع منتجًا ثانيًا.
  2. استخدم التسلسل الاختياري (optional chaining) للمسار كله، حتى لا يتسبب حقل غير متوقع في خطأ أثناء رسم React.

يعطيك onChange القيمة {available, adFree, owned, ownedIds, products} فورًا وعند كل تغيير، ويُرجع دالة لإلغاء الاشتراك، فيكون المشتري العائد مفتوحًا بالفعل منذ أول رسم. في Next.js أضف 'use client' في السطر 1 وابنِ بـ output: 'export'.

أيضًا: AppMintIAP.available و.isOwned(id) و.owned() و.buy(id) و.state() و.refresh(). تسميات data-iap-buy تعمل في JSX أيضًا، والأزرار التي يركّبها React لاحقًا تُلتقط تلقائيًا.

تفضّل التحكم بالجسر الخام بنفسك؟ كل ما سبق مبني على هذه:

  • 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}

هل تبيع ترقية واحدة فقط لإزالة الإعلانات؟ استخدم الزوج المدمج الأبسط بدلًا من ذلك، وفعّل «إزالة الإعلانات (IAP)» في الخطوة 3 من المعالج:

var iap = window.WebToApk;

if (iap && !iap.isPremium()) {
  showRemoveAdsButton(function onTap() {
    iap.startRemoveAdsPurchase();
  });
}

window.addEventListener('appmint:premium', function (e) {
  if (e.detail.premium) hideRemoveAdsButton();
});

إزالة الإعلانات (IAP)

إعداد فوترة Google Play

1إنشاء منتج مُدار#

في لوحة Google Play Console الخاصة بك:

  • اذهب إلى «تحقيق الدخل» > «المنتجات داخل التطبيق»
  • اضغط «إنشاء منتج»
  • أدخل معرّف منتج (مثل 'remove_ads_premium')
  • حدد اسمًا (مثل «إزالة كل الإعلانات») ووصفًا
  • حدد السعر واضغط «حفظ»
  • اضغط «تفعيل» لإتاحته للاختبار

تبيع إزالة الإعلانات كاشتراك بدلًا من ذلك؟ أنشئه تحت «تحقيق الدخل» > «الاشتراكات» بخطة أساسية بتجديد تلقائي، وفعّل الخطة الأساسية والاشتراك معًا. تدوم حالة بلا إعلانات ما دام الاشتراك نشطًا، وتعود الإعلانات تلقائيًا عند انتهائه.

2احصل على مفتاح الترخيص#

يتطلب Google Play مفتاحًا عامًا RSA للتحقق من المشتريات بأمان.

في Play Console:

  • اذهب إلى «تحقيق الدخل» > «إعداد تحقيق الدخل»
  • تحت «الترخيص»، انسخ «المفتاح العام RSA بترميز Base64»
  • ⚠️ مهم: ستحتاج إلى لصق هذا في المولّد لاحقًا.
Example Key Format:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...

3الإعداد في مولّد WebToApk#

في تطبيق المولّد:

  1. فعّل مفتاح «إزالة الإعلانات (IAP)» في الخطوة 3
  2. أدخل «معرّف المنتج» نفسه تمامًا الذي أنشأته في الخطوة 1
  3. الصق «مفتاح ترخيص Base64» من الخطوة 2
  4. أنشئ APK/AAB الخاص بك

تعرض أكثر من خطة بلا إعلانات (مثل اشتراك شهري وفتح دائم لمرة واحدة)؟ ضع كل معرّفات المنتجات في هذا الحقل مفصولة بفواصل، فامتلاك أي منها يزيل الإعلانات. المعرّف الأول المدرج هو الذي يشتريه عنصر قائمة «إزالة الإعلانات» المدمج، فضع خطتك الافتراضية أولًا.

Product ID: remove_ads_premium

Two plans:
remove_ads_monthly,remove_ads_lifetime

4إعداد مختبري الترخيص#

لاختبار المشتريات دون إنفاق مال حقيقي:

  • في Play Console، اذهب إلى «الإعداد» > «اختبار الترخيص»
  • أضف عنوان Gmail الخاص بك إلى قائمة «مختبري الترخيص»
  • تأكد من ضبط «استجابة الترخيص» على «RESPOND_NORMALLY»

5اختبر في المسار الداخلي أو المغلق#

المشتريات داخل التطبيق لا تعمل إلا عندما يكون التطبيق مثبّتًا عبر متجر Play (مسارات الاختبار):

  1. ارفع ملف AAB الذي أنشأته إلى مسار «الاختبار الداخلي» أو «الاختبار المغلق»
  2. شارك رابط الانضمام للاختبار مع حسابات الاختبار الخاصة بك
  3. نزّل التطبيق من متجر Play
  4. اضغط زر «إزالة الإعلانات» في تطبيقك لاختبار التدفق!
Note: Purchases will show a 'Test instrument, always approves' message for license testers.

وصفات الكود

الخطوات أعلاه تغطي الأساسيات: التسميات الأربع، وصفحة عادية كاملة، وسطر React الواحد. هذه الوصفات تغطي ما لا تغطيه: خطط عدة يحل بعضها محل بعض، واشتراكات تنتهي، وخطافات الأطر، والجسر الخام كاملًا، وجداول المرجع.

القاعدة الوحيدة#

يُمنح الشراء فقط عندما يؤكده Google. ليس عند النقر على الزر، ولا عند فتح الشاشة، ولا عند عودة المستخدم إلى صفحتك. كل ما يلي طريقة مختلفة للتعبير عن هذه القاعدة الواحدة.

وقت التشغيل يفرضها بالفعل: AppMintIAP يعيد قراءة الملكية من الجسر عند التشغيل، وعند كل شراء، وعند كل استرداد، وكلما أُعيد فتح التطبيق. تبقى الملكية بعد إعادة التثبيت وعلى هاتف جديد ودون اتصال، لأنها موجودة في حساب Google للمشتري وليس في صفحتك.

لا تفتح المنتج أبدًا داخل معالج النقر، ولا تحتفظ بالاستحقاق في localStorage فقط. مسح مخزن المتصفح يجب ألا يكلّف عميلًا دافعًا ما اشتراه، ونسخ قيمة localStorage يجب ألا يمنحه مجانًا.

الوصفة 1 - جدول أسعار بأسعار حية#

ثلاث خطط يحل شراء أي منها محل الباقي. 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، يُترك ببساطة بلا سعر. يبقى زره قابلًا للضغط عن قصد: الضغط عليه يبلّغ بالسبب الحقيقي بدل ألا يفعل شيئًا.

الوصفة 2 - React#

onChange يُطلَق فورًا بالحالة الحالية ومرة أخرى عند كل تغيير، ويُرجع دالة إلغاء الاشتراك الخاصة به، فهو بالضبط الشكل الذي يريده useEffect.

hooks/useIap.js
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.

الوصفة 3 - Vue#

البوابة نفسها في Vue 3
<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>

الوصفة 4 - الاشتراكات#

يُشترى الاشتراك تمامًا مثل المنتج لمرة واحدة، والفرق أنه قد ينتهي. 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.

لا تحتاج إلى الاستعلام الدوري ولا إلى تاريخ انتهاء. يستعيد Play المجموعة المملوكة عند كل تشغيل، فالاشتراك المنتهي يتوقف ببساطة عن الظهور في ownedIds.

فعّل كلًا من الاشتراك وخطته الأساسية في Play Console. الاشتراك ذو الخطة الأساسية غير المفعّلة لا يُرجع أي تفاصيل، وسيبلّغ الزر «غير متاح على Google Play بعد».

الوصفة 5 - الجسر الخام بلا مساعد#

كل ما سبق مبني على ست دوال وخمسة أحداث. إذا أردت التحكم الكامل في الواجهة، فاستخدمها مباشرة.

السطح الكامل في ملف واحد
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() بحيث يكون تشغيله مرتين غير ضار، ولا تضطر أبدًا إلى تتبع ما إذا كنت شغّلته من قبل.

الوصفة 6 - تراجع بصدق خارج التطبيق#

عادةً يجب أن تُفتح الصفحة نفسها في متصفح عادي أيضًا، أثناء التطوير أو لأنك تنشرها على الويب كذلك. هناك لا يوجد 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;
}

المرجع#

كل ما يمكن للصفحة استدعاؤه أو الاستماع إليه أو وسمه.

window.AppMintIAP - المساعد الذي يحقنه وقت التشغيل
الاستدعاءماذا يفعل
AppMintIAP.availablefalse في المتصفح، أو عندما لم تُفعَّل المشتريات داخل التطبيق لهذا البناء. افحصه قبل عرض أي شيء للبيع.
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 المضافة منذ آخر مسح. نادرًا ما تحتاجه، فالتشغيل يراقب DOM.
window.WebToApk - الجسر الذي تحته
الاستدعاءيُرجع
purchase(productId)لا شيء. يفتح الشاشة، وتصل النتيجة كحدث.
getProducts(requestId, idsJson)لا شيء. تصل الإجابة كحدث appmint:products يحمل نفسه requestId.
getOwnedProducts()مصفوفة JSON لمعرّفات المنتجات المملوكة، كنص.
isOwned(productId)قيمة منطقية: منتج لمرة واحدة مملوك، أو اشتراك نشط حاليًا.
isPremium()قيمة منطقية: استحقاق «إزالة الإعلانات» تحديدًا.
startRemoveAdsPurchase()لا شيء. يفتح الشاشة لأول معرّف في حقل «إزالة الإعلانات» في المعالج، وامتلاك أي معرّف مدرج هناك يزيل الإعلانات. تصل النتيجة كحدث 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[]}انتهت الاستعادة عند التشغيل، أو سحب استرداد شيئًا. يحمل كل معرّف مملوك.
سمات HTML
السمةتشغيلماذا يفعل
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"يحدده وقت التشغيلعلى زر منتج مملوك، لتتمكن من تنسيقه.
أسباب الفشل في appmint:purchase-failed
السببما حدث فعلًا
not_foundلا يعرف Google معرّف المنتج هذا: لم يُنشأ، أو لم يُفعَّل، أو لم يُثبَّت التطبيق من متجر Play.
billing_errorتعذّر على Play بدء الدفع. غالبًا مؤقت، وعلى المشتري المحاولة مجددًا.
pendingطريقة دفع بطيئة (نقدًا، تحويل مصرفي). يُفتح المنتج تلقائيًا عندما يؤكد Google، لا تعتبر هذا فشلًا.
disabledلم تُفعَّل المشتريات داخل التطبيق لهذا البناء.
unknownأي شيء آخر.

قبل أن تنشر#

  1. كل معرّف منتج في الكود موجود في Play Console ومُفعَّل، وللاشتراك يشمل ذلك الخطة الأساسية أيضًا.
  2. يحمل البناء المعرّفات نفسها، ومفتاح الترخيص RSA بصيغة Base64 من إعداد تحقيق الدخل.
  3. عنوان Gmail الخاص بك موجود في الإعداد ← اختبار الترخيص، مع ضبط الاستجابة على RESPOND_NORMALLY.
  4. رفعت ملف AAB إلى الاختبار الداخلي أو المغلق وثبّتَّه من رابط متجر Play، فملف APK المثبّت يدويًا لا يمكنه شراء أي شيء أبدًا.
  5. اشتريت كل منتج كمختبر ترخيص ورأيت «أداة اختبار، توافق دائمًا».
  6. ألغيت التثبيت وأعدته، وعادت عملية الشراء من تلقاء نفسها.
  7. أوقفت الشبكة وما زال المحتوى المدفوع مفتوحًا.
  8. استرجعت مبلغ عملية شراء تجريبية في Play Console وشاهدت التطبيق يُقفل مجددًا.

أخطاء تكلّف مالًا#

  • الفتح داخل معالج النقر. أي شخص يفتح الشاشة ثم يتراجع يحصل على المنتج مجانًا.
  • تخزين الاستحقاق في localStorage فقط. مسح بيانات الموقع يسلب العميل شيئًا دفع ثمنه، ونسخ قيمة يمنحه مجانًا.
  • كتابة السعر ثابتًا في HTML. سيكون خاطئًا في كل بلد آخر وقديمًا في اليوم الذي تغيّره فيه. استخدم {price} أو products[id].price.
  • بيع سلع مادية أو خدمة في الواقع عبر فوترة Play. يشترط Google العكس: يجب أن تستخدم هذه معالج دفع عاديًا. وهذا هو غرض دليل Stripe.
  • الاختبار على APK مثبّت يدويًا. المشتريات داخل التطبيق لا تعمل إلا عندما يكون التطبيق مثبّتًا من متجر Play. لا شيء تكتبه يمكنه تغيير ذلك.
  • اعتبار pending فشلًا. دفع المشتري بطريقة بطيئة، ويُفتح المنتج تلقائيًا عندما يؤكد Google ذلك.

تُنشأ هذه الصفحة من دليل التكامل الموجود داخل التطبيق نفسه، لذا فهي تعكس بدقة ما يقوله الإصدار الحالي. اقرأها بصيغة Markdown.

تمت المراجعة مقابل الدليل المُرفق بتاريخ 2026-10-22.