> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwacks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# الشراء داخل التطبيق

> بِع عناصر المتجر بمال حقيقي عبر App Store وGoogle Play.

العنصر المدفوع بمال حقيقي هو عنصر متجر يدفع اللاعب ثمنه في App Store أو Google Play. يتقاضى المتجر
المبلغ من اللاعب، ثم يسأل Flock المتجرَ هل عملية الشراء حقيقية، ثم يسلّم العنصر. وتستدعي لعبتك الدالة
نفسها التي تستدعيها لأي عنصر: `Shop.PurchaseAsync(item.Id)`.

```
Player taps Buy ──▶ store charges ──▶ Flock checks with the store ──▶ item delivered ──▶ store purchase finished
```

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

> [!NOTE]
>
> تحتاج المشتريات بالمال الحقيقي إلى حزمة تطوير Flock لـ Unity بالإصدار 1.98.0 أو أحدث، وإلى Unity IAP 5
> (`com.unity.purchasing` 5.x) في المشروع. ولا تحتاج مشتريات المحفظة إلى أيٍّ منهما: راجع
> [المتاجر والمخزون](https://docs.qwacks.com/ar/guides/shops-and-inventory.md). وعلى Android، اجعل **Minimum API Level** 23 أو
> أعلى (**Player Settings > Other Settings**): فمكتبة Google Play Billing في Unity IAP 5 تحتاجه، ومشروع
> Unity 2021.3 يبدأ بـ 22، فيتوقف بناؤه بخطأ في دمج ملف البيان.

## ما الذي تُعدّه

1. **اربط كل متجر بـ Flock** من لوحة التحكم، بمفاتيح لعبتك الخاصة.
2. **سعّر العناصر بمال حقيقي** وأعطِ كلًّا منها معرّف منتجه في المتجر.
3. **أنشئ المنتجات في المتجر** بتلك المعرّفات.
4. **اربط المتجر داخل لعبتك** مرة واحدة عند البدء.
5. **تحقّق من الإعداد** من داخل اللعبة.

**1. اربط المتاجر بـ Flock**

افتح **Monetization > Payments** في لوحة تحكم Flock واربط كل متجر تبيع فيه. يستخدم Flock هذه المفاتيح
ليسأل المتجر عن عملية الشراء، ولا تصل أبدًا إلى جهاز اللاعب.

> [tab] Google Play
>
> | الحقل | ما تُدخله |
> | - | - |
> | Package name | اسم حزمة تطبيقك كما في Google Play Console (مثلًا `com.studio.game`) |
> | Service account JSON | النص الكامل لمفتاح JSON لحساب خدمة في Google Cloud |
>
> لإنشاء حساب الخدمة:
>
> 1. في Google Cloud console، وفي أي مشروع، فعّل **Google Play Android Developer API**.
> 2. أنشئ هناك **حساب خدمة** (service account)، وأضف إليه **مفتاح JSON**. يُنزَّل المفتاح ملفًّا.
> 3. في Google Play Console، تحت **Users and permissions**، ادعُ عنوان البريد الإلكتروني لحساب الخدمة،
>    وامنحه تطبيقك، واسمح له بـ **View financial data** و**Manage orders and subscriptions**.
> 4. الصق النص الكامل لملف JSON في **Service account JSON**.
>
> قد تستغرق Google وقتًا لتقبل حساب خدمة جديدًا. وإلى أن تقبله، يخبرك التحقّق من الإعداد أدناه بأن
> المتجر رفض مفاتيحك. وفي اختبارنا، سرى مفعوله بعد دقيقة من إعادة حفظ أحد منتجات التطبيق في Play
> Console.

> [tab] App Store
>
> | الحقل | ما تُدخله |
> | - | - |
> | Issuer ID | من App Store Connect، **Users and Access > Integrations > In-App Purchase** |
> | Key ID | المفتاح الذي تُنشئه في تلك الصفحة |
> | Private key | النص الكامل لملف `.p8` الذي يُنزَّل به ذلك المفتاح |
> | Bundle ID | معرّف حزمة تطبيقك (bundle identifier) |

وتعرض الصفحة أيضًا **عنوان إشعارات** لكل متجر. أعطِه للمتجر (App Store Server Notifications في App
Store Connect، وreal-time developer notifications في Google Play) ليعلم Flock بعمليات الاسترداد.

**2. سعّر العناصر بمال حقيقي**

يكون العنصر مدفوعًا بمال حقيقي حين تكون عملته إحدى **Real Currencies** في إعدادات العملات لإصدار لعبته.
ويكون سعره بأصغر وحدة للعملة: `399` بعملة `SAR` تعني SAR 3.99.

وعلى كل عنصر من هذه العناصر، تحت **Store product IDs**، أدخل معرّف منتجه لكل متجر، تمامًا كما يكتبه
المتجر. ويحدّد نوع العنصر نوع المنتج في المتجر:

| نوع العنصر | منتج المتجر | ما يحصل عليه اللاعب |
| - | - | - |
| حزمة عملة | قابل للاستهلاك | مكافآتها، مباشرةً في المحفظة |
| قابل للاستهلاك | قابل للاستهلاك | مدخل في المخزون، يُستخدم لاحقًا بـ `ConsumeOneAsync` |
| قياسي | لمرة واحدة (غير قابل للاستهلاك) | مدخل في المخزون. ويبيعه المتجر مرة واحدة لكل حساب متجر |

**3. أنشئ المنتجات في المتجر**

أنشئ كل منتج بالمعرّف نفسه: في Google Play Console تحت **Monetize > Products > One-time products**،
وفي App Store Connect تحت **In-App Purchases** لتطبيقك. ثم فعّله. والسعر الذي يراه اللاعب هو ما
يحدّده المتجر لبلده.

**4. اربط المتجر داخل لعبتك**

اربطه مرة واحدة بعد بدء حزمة التطوير، واحتفظ بالاتصال طوال الجلسة. وعلى الهاتف يُعثر على المتجر
تلقائيًا، وعلى Windows وmacOS وLinux وWebGL يربط الاستدعاء نفسه الدفع عبر صفحة ويب بدلًا منه
([الدفع عبر الويب](https://docs.qwacks.com/ar/guides/web-checkout.md)).

```csharp theme={null}
FlockStore store;

async void Start()
{
    store = await FlockStore.ConnectAsync();

    // Every real-money purchase Flock delivers, the ones PurchaseAsync answers included:
    // refresh what the screen shows here.
    store.PurchaseCompleted += (item, result) => RefreshTheShop();

    // A purchase the player paid for that Flock could not deliver. It comes back at the next launch.
    store.PurchaseNotDelivered += purchase => Debug.LogWarning(purchase.Error.Message);
}

void OnDestroy() => store?.Dispose();
```

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

**5. تحقّق من الإعداد**

تسأل `CheckSetupAsync` المتجرَ وخادمَ Flock هل كل شيء في مكانه، دون شراء أي شيء. وتحتاج إلى لاعب مسجَّل
دخوله، وترمي `FlockAuthException` من دونه، لذا شغّلها من زر للمطوّر بعد تسجيل الدخول أثناء الإعداد.

```csharp theme={null}
FlockStoreSetupCheck check = await store.CheckSetupAsync();

if (check.IsReady)
    Debug.Log("The store has every item, and Flock can check purchases with your keys.");
foreach (ShopItem item in check.NotFoundInTheStore)
    Debug.Log($"{item.Name}: the store has no product with this item's id.");
foreach (ShopItem item in check.MissingProductId)
    Debug.Log($"{item.Name}: no product id for this store on the item.");
if (check.ServerProblem != null)
    Debug.Log(check.ServerProblem.Message);   // the keys in Monetization > Payments, and how to fix them
```

## اعرض سعر المتجر

اعرض السعر الذي يعطيه المتجر، وهو بعملة اللاعب ومع ضرائبه، بدل سعر العنصر في Flock.

```csharp theme={null}
FlockStorePrice price = store.GetPrice(item.Id);
string label = price != null ? price.Text : "Not available";   // e.g. "SAR 4.59", "$1.09"
```

وتكون قيمة `GetPrice` هي `null` حين لا يجد المتجر المنتج. والأسعار في التعليق أمثلة فقط: فالنص هو نص
المتجر نفسه. وفي الدفع عبر صفحة ويب يعطي `GetPrice` سعر العنصر نفسه، ولا تكون قيمته `null` أبدًا.

## الشراء

```csharp theme={null}
try
{
    PurchaseResult bought = await FlockClient.Instance.Shop.PurchaseAsync(item.Id);

    if (bought.IsPaid)
        ShowReward(bought.Granted);          // delivered
    else
        ShowWaitingForPayment();             // pending: PurchaseCompleted reports it once paid
}
catch (FlockStoreException e) when (e.Reason == FlockStoreFailureReasons.Cancelled)
{
    // The player closed the store's dialog. Nothing was charged.
}
catch (FlockStoreException e) when (e.Reason == FlockStoreFailureReasons.AlreadyOwned)
{
    // This store account already owns the one-time item: offer Restore purchases.
}
catch (FlockStoreException e)
{
    // The store did not take the payment (declined, not available, store down...). Nothing was charged.
    Debug.Log(e.Reason + ": " + e.Message);
}
catch (FlockException e) when (e.OutcomeUnknown)
{
    // No answer said whether Flock delivered. Read the wallet and inventory again.
}
catch (FlockException e)
{
    // The player paid, and Flock refused the purchase (e.ErrorCode says why). The store keeps it,
    // and it is sent again at the next launch. PurchaseNotDelivered reports it too.
    Debug.LogWarning(e.Message);
}
```

كل الحالات باختصار:

| ما الذي يحدث | ما تفعله `PurchaseAsync` | ما يراه اللاعب |
| - | - | - |
| دُفع وسُلّم | تعود، و`IsPaid` صحيحة | المكافأة |
| ينتظر المتجر الدفع (طريقة دفع بطيئة، أو «Ask to Buy») | تعود، و`Status` قيمتها pending | «بانتظار الدفع»؛ ثم يتبعها `PurchaseCompleted` بعد الدفع |
| ألغى اللاعب | ترمي `FlockStoreException` بالسبب `Cancelled` | لا شيء؛ ولم يُخصم شيء |
| رُفض الدفع (App Store) | `FlockStoreException` بالسبب `PaymentDeclined` | جرّب طريقة دفع أخرى؛ ولم يُخصم شيء |
| عنصر لمرة واحدة مملوك سلفًا | `FlockStoreException` بالسبب `AlreadyOwned` | اعرض **استعادة المشتريات** |
| عملية شراء سابقة له لا تزال تنتظر | `FlockStoreException` بالسبب `AlreadyPending` | يصل عبر `PurchaseCompleted` |
| المنتج غير موجود في المتجر | `FlockStoreException` بالسبب `ProductUnavailable` | تحقّق من معرّف المنتج (يسرده التحقّق من الإعداد) |
| المتجر غير متصل أو معطَّل، أو رفض Google Play الدفع (يُبلغ Google عن الحالتين بالطريقة نفسها) | `FlockStoreException` بالسبب `StoreUnavailable` | تحقّق من الاتصال ومن طريقة الدفع، ثم حاول مجددًا؛ ولم يُخصم شيء |
| لا أحد مسجَّل دخوله في المتجر | `FlockStoreException` بالسبب `NotSignedInToTheStore` | سجّل الدخول إلى المتجر، ثم حاول مجددًا |
| دُفع، ورفضه Flock | ترمي `FlockException` برمزه | يُسلَّم في تشغيل لاحق بعد الإصلاح |
| لا ردّ من Flock | `FlockException`، و`OutcomeUnknown` صحيحة | اقرأ المحفظة والمخزون من جديد |

وإن ألغيتَ `CancellationToken` الخاص بالاستدعاء، تستمر عملية الشراء في المتجر، ويُبلغ عنها
`PurchaseCompleted` بعد الدفع. وإن سجّل اللاعب خروجه ومربع حوار المتجر مفتوح، ينتهي الاستدعاء كذلك
بالإلغاء، وتُسلَّم عملية الشراء في المتجر بعد الدفع إلى اللاعب الذي اشتراها عند تسجيل دخول لاحق، أو عبر استعادة المشتريات.

## استعادة المشتريات

يستعيد اللاعب على هاتف جديد، أو بعد إعادة التثبيت، عناصره ذات المرة الواحدة بزر **استعادة المشتريات**.
ابدأها من زر بعد أن يسجّل اللاعب الدخول إلى Flock: فمن دون لاعب مسجَّل دخوله ترمي `RestoreAsync`
الاستثناء `FlockAuthException`. وفي App Store تطلب أيضًا من اللاعب تسجيل الدخول إلى المتجر.

```csharp theme={null}
FlockStoreRestore restore = await store.RestoreAsync();

foreach (ShopItem item in restore.Restored)
    Debug.Log("Restored " + item.Name);
foreach (FlockUndeliveredPurchase purchase in restore.NotDelivered)
    Debug.Log(purchase.ProductId + ": " + purchase.Error.Message);   // e.g. bought by another Flock player
```

وعملية الشراء التي سُلّمت من قبل لا تمنح شيئًا مرة أخرى. وتعود العناصر ذات المرة الواحدة إلى لاعب
Flock الذي اشتراها، لذا يجب أن يسجّل اللاعب الدخول إلى حساب Flock نفسه (فتسجيل الدخول بالجهاز مرتبط
بالجهاز؛ اربط الحساب ليحتفظ بمشترياته عبر الأجهزة). راجع [ربط الحسابات](https://docs.qwacks.com/ar/guides/account-linking.md).

## مشتريات تنتهي لاحقًا

تنتهي بعض المشتريات بعد الاستدعاء: دفعة تُسوّى لاحقًا، أو عملية شراء قاطعها تعطّل أو انقطاع اتصال.
يعيد المتجر كلًّا منها حين تتصل به اللعبة؛ وما إن يُسجّل اللاعب دخوله حتى يسلّمها Flock ويُبلغ عنها
`PurchaseCompleted`. وما تعذّر فيه الوصول إلى Flock يُرسَل من جديد في التشغيل التالي، أو فورًا حين يضغط
اللاعب **استعادة المشتريات**. اشترك في `PurchaseCompleted` قبل أن يحتاجه أي شيء آخر، واستخدمه لتحديث
المتجر والمحفظة.

## إيقاف بيع عنصر

اجعل العنصر، أو متجره، **غير نشط** في لوحة التحكم. فلا تعرضه قراءات المتجر للاعبين من فورها، ويتحقّق
`PurchaseAsync` مع Flock قبيل نافذة المتجر مباشرة، فإن كانت اللعبة لا تزال تعرض العنصر رُفض الشراء بـ
`ShopItemNotForSale` ولا يقتطع المتجر شيئًا. أما عملية الشراء التي دفعها اللاعب فتُسلَّم مع ذلك: إذ تحتفظ
اللعبة بالمنتجات التي باعتها، فتُسلَّم عملية الشراء غير المكتملة لعنصر غير نشط عند التشغيل التالي، وتعيد
الاستعادة العناصر التي تُشترى مرة واحدة.

## اختبر قبل الإطلاق

* **في المحرّر**، تحدّد منصة البناء ذلك: فإن اخترت Android أو iOS أجاب المتجر الوهمي لـ Unity IAP. وعلى منصة
  بناء أخرى، سمِّ المتجر الذي تختبر به:
  `FlockStore.ConnectAsync(new FlockStoreOptions { PaymentProvider = PaymentProviders.GooglePlay })`. ويعمل التحقّق من الإعداد هناك ويختبر مفاتيحك،
  لكن Flock يرفض مشتريات المتجر الوهمي، فتنتهي عملية الشراء في المحرّر دون تسليم.
* **Google Play**: ارفع بناءً إلى مسار **internal testing**، وأضف المختبرين تحت **License testing** في
  Play Console، وثبّت اللعبة من رابط الاختبار في Play Store. ولا تُحتسب مشتريات المختبر.
* **App Store**: استخدم مختبر **Sandbox** من App Store Connect على جهاز.

## الإيرادات

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

## حين يفشل شيء

يذكر `Message` لكل فشل ما الخطأ، وفي سطر `Fix:` كيف تصلحه. وهذه رموز عملية الشراء في المتجر:

| الرمز | ما الذي حدث | ما العمل |
| - | - | - |
| `PaymentProviderStoreCredentialsRejected` | رفض المتجر مفاتيح لعبتك | تحقّق من المفاتيح في **Monetization > Payments**، ومن صلاحية حساب الخدمة في Play Console. وتمرّ عملية الشراء نفسها بعد الإصلاح |
| `PaymentProviderCredentialsUnreadable` | لا يستطيع الخادم قراءة المفاتيح المخزّنة | أدخل المفاتيح من جديد |
| `PaymentProviderProviderNotFound` | المتجر غير مربوط، أو موقوف مؤقتًا | اربطه، أو أعد تفعيل **Active** |
| `PaymentProviderReceiptProductMismatch` | عملية الشراء لمنتج غير منتج العنصر | تحقّق من **Store product IDs** للعنصر |
| `PaymentProviderReceiptBelongsToAnotherPlayer` | اشتراها لاعب Flock آخر، أو استردّها | سجّل الدخول باللاعب الذي اشتراها |
| `PaymentProviderReceiptPending` | لا يزال المتجر ينتظر الدفع | لا شيء: تُرسَل من جديد في التشغيل التالي |
| `PaymentProviderStoreUnavailable` | لم يستطع Flock الوصول إلى المتجر | لا شيء: تُرسَل من جديد في التشغيل التالي |
| `PaymentProviderReceiptInvalid` | لم يؤكّد المتجر عملية الشراء | اقرأ السبب: عملية شراء مزيّفة، أو مستردّة، أو لحظة سيئة لدى المتجر |

## Unity IAP 4

تحتفظ اللعبة التي تعمل بـ Unity IAP 4.x بشيفرة الشراء الخاصة بها، وتسلّم كل عملية شراء إلى Flock عبر
`Shop.RedeemStoreReceiptAsync`، ولا تُنهيها في المتجر إلا بعد أن تعود. والملف
`Docs~/recipes/FlockUnityIap4Store.cs` في حزمة التطوير نسخة جاهزة تنسخها.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.