دمج "مكتبة الفوترة في Google Play" في تطبيقك

يوضّح هذا الموضوع كيفية دمج Google Play Billing Library في تطبيقك لبدء بيع المنتجات.

مدة الشراء

في ما يلي خطوات الشراء المعتادة لعملية شراء لمرة واحدة أو اشتراك.

  1. أظهر للمستخدم ما يمكنه شراؤه.
  2. ابدأ مسار الشراء للمستخدم لقبول عملية الشراء.
  3. أثبِت عملية الشراء على خادمك.
  4. تقديم المحتوى للمستخدم
  5. الإقرار بإرسال المحتوى بالنسبة للمنتجات الاستهلاكية، استهلك عملية الشراء حتى يتمكن المستخدم من شراء العنصر مرة أخرى.

يتم تجديد الاشتراكات تلقائيًا إلى أن يتم إلغاؤها. يمكن أن يمر الاشتراك في الحالات التالية:

  • نشِط: أنّ المستخدم في وضع جيد ويمكنه الوصول إلى الاشتراك.
  • تم الإلغاء: ألغى المستخدم الاشتراك، ولكنه لا يزال بإمكانه الوصول إلى أن تنتهي صلاحيته.
  • في فترة السماح: واجه المستخدم مشكلة في الدفع، ولكن لا يزال بإمكانه الوصول أثناء إعادة محاولة استخدام طريقة الدفع من خلال Google.
  • معلّق: واجه المستخدم مشكلة في الدفع ولم يعُد بإمكانه الوصول إلى الحساب بينما تعيد Google محاولة استخدام طريقة الدفع.
  • متوقف مؤقتًا: أوقف المستخدم مؤقتًا إمكانية وصوله ولا يملك إذن الوصول إلى أن يستأنف.
  • منتهي الصلاحية: ألغى المستخدم الاشتراك وفقد إمكانية الوصول إليه. يُعتبَر المستخدم متوقفًا عن الاستخدام عند انتهاء الصلاحية.

تهيئة الاتصال بـ Google Play

الخطوة الأولى لدمج خدمة الفوترة مع نظام الفوترة في Google Play هي إضافة Google Play Billing Library إلى تطبيقك وإعداد عملية الربط.

إضافة تبعية Google Play Billing Library

أضِف تبعية Google Play Billing Library إلى ملف build.gradle لتطبيقك على النحو الموضّح:

رائع

dependencies {
    def billing_version = "6.2.0"

    implementation "com.android.billingclient:billing:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "6.2.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

وإذا كنت تستخدم لغة البرمجة Kotlin، فإنّ وحدة KTX لمكتبة الفوترة في Google Play تحتوي على إضافات وأدوات كورروتينية في لغة Kotlin تتيح لك كتابة لغة اصطلاحية عند استخدام Google Play Billing Library. لتضمين هذه الإضافات في مشروعك، أضِف التبعية التالية إلى ملف build.gradle لتطبيقك كما هو موضّح:

رائع

dependencies {
    def billing_version = "6.2.0"

    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "6.2.0"

    implementation("com.android.billingclient:billing-ktx:$billing_version")
}

إعداد BillingClient

بعد إضافة تبعية إلى Google Play Billing Library، عليك إعداد مثيل BillingClient. إنّ BillingClient هي الواجهة الرئيسية للتواصل بين "مكتبة الفوترة في Google Play" وبقية تطبيقك. يوفّر BillingClient طرقًا ملائمة، متزامنة وغير متزامنة، للعديد من عمليات الفوترة الشائعة. ننصحك بشدة بفتح اتصال BillingClient واحد نشط في الوقت نفسه لتجنُّب تكرار عمليات استدعاء PurchasesUpdatedListener لحدث واحد.

لإنشاء BillingClient، استخدِم newBuilder(). يمكنك إضافة أي سياق إلى newBuilder()، ويستخدمه BillingClient للحصول على سياق التطبيق. يعني ذلك أنّه لا داعي للقلق بشأن تسرُّب الذاكرة. لتلقّي آخر الأخبار حول عمليات الشراء، يجب أيضًا الاتصال على الرقم setListener()، مع إدراج إشارة مرجعية إلى PurchasesUpdatedListener. يتلقّى هذا المستمع تحديثات لجميع عمليات الشراء في تطبيقك.

Kotlin

private val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // To be implemented in a later section.
   }

private var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .build()

Java

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // To be implemented in a later section.
    }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases()
    .build();

الربط بمتجر Google Play

بعد إنشاء BillingClient، يجب الربط بGoogle Play.

للربط بـ Google Play، يمكنك الاتصال بالرقم startConnection(). إنّ عملية الربط غير متزامنة، وعليك تنفيذ BillingClientStateListener لتلقّي معاودة الاتصال بعد اكتمال إعداد العميل وعندما يصبح جاهزًا لتقديم المزيد من الطلبات.

يجب أيضًا تنفيذ منطق إعادة المحاولة لمعالجة عمليات الاتصال المفقودة إلى Google Play. لتنفيذ منطق إعادة المحاولة، عليك إلغاء طريقة معاودة الاتصال في onBillingServiceDisconnected() والتأكّد من أنّ BillingClient يستدعي طريقة startConnection() لإعادة الاتصال بمتجر Google Play قبل إرسال طلبات أخرى.

يوضح المثال التالي كيفية بدء اتصال واختبار أنه جاهز للاستخدام:

Kotlin

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

Java

billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
});

عرض المنتجات المتاحة للشراء

بعد الربط بـ Google Play، تكون على استعداد للاستعلام عن المنتجات المتاحة وعرضها للمستخدمين.

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

لطلب تفاصيل عن منتج داخل التطبيق، يمكنك الاتصال بالرقم queryProductDetailsAsync().

لمعالجة نتيجة العملية غير المتزامنة، يجب أيضًا تحديد مستمع ينفذ واجهة ProductDetailsResponseListener. يمكنك بعد ذلك إلغاء السمة onProductDetailsResponse() التي ترسل إشعارًا إلى المستمع عند انتهاء طلب البحث، كما هو موضّح في المثال التالي:

Kotlin

val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build()

billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
    billingResult,
    productDetailsList ->
      // check billingResult
      // process returned productDetailsList
}

Java

QueryProductDetailsParams queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build();

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
        public void onProductDetailsResponse(BillingResult billingResult,
                List<ProductDetails> productDetailsList) {
            // check billingResult
            // process returned productDetailsList
        }
    }
)

عند إجراء طلب بحث عن تفاصيل منتج، مرِّر مثيل QueryProductDetailsParams الذي يحدد قائمة بسلاسل معرّفات المنتج التي تم إنشاؤها في Google Play Console مع ProductType. يمكن أن تكون القيمة ProductType إما ProductType.INAPP للمنتجات التي يتم تحصيل سعرها مرة واحدة أو ProductType.SUBS للاشتراكات.

الاستعلام باستخدام إضافات Kotlin

إذا كنت تستخدم إضافات Kotlin، يمكنك طلب البحث عن تفاصيل المنتج داخل التطبيق من خلال استدعاء وظيفة الإضافة queryProductDetails().

يستفيد queryProductDetails() من الكوروتينات في لغة Kotlin بحيث لا تحتاج إلى تحديد مستمع منفصل. بدلاً من ذلك، يتم تعليق الدالة حتى يكتمل الاستعلام، وبعد ذلك يمكنك معالجة النتيجة:

suspend fun processPurchases() {
    val productList = listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("product_id_example")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )
    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)

    // leverage queryProductDetails Kotlin extension function
    val productDetailsResult = withContext(Dispatchers.IO) {
        billingClient.queryProductDetails(params.build())
    }

    // Process the result.
}

نادرًا ما تكون بعض الأجهزة غير متوافقة مع ProductDetails وqueryProductDetailsAsync()، ويرجع ذلك عادةً إلى إصدارات قديمة من خدمات Google Play. لضمان تقديم الدعم المناسب لهذا السيناريو، تعرَّف على كيفية استخدام ميزات التوافق مع الأنظمة القديمة في دليل نقل البيانات للإصدار 5 من Play Billing Library.

معالجة النتيجة

تخزِّن Google Play Billing Library نتائج طلبات البحث في List من عناصر ProductDetails. يمكنك بعد ذلك استدعاء مجموعة متنوعة من الطرق لكل عنصر ProductDetails في القائمة لعرض المعلومات ذات الصلة بالمنتج داخل التطبيق، مثل سعره أو وصفه. لعرض معلومات تفاصيل المنتج المتاحة، راجِع قائمة الطرق في الفئة ProductDetails.

قبل عرض سلعة للبيع، تأكَّد من أنّ المستخدم لا يملك السلعة حتى الآن. إذا كان لدى المستخدم مادة استهلاكية لا تزال في مكتبة العناصر الخاصة به، فيجب أن يستهلك العنصر قبل أن يتمكن من شرائه مرة أخرى.

قبل توفير اشتراك، تأكَّد من أنّ المستخدم غير مشترك حاليًا. لاحظ أيضًا ما يلي:

  • يعرض queryProductDetailsAsync() تفاصيل المنتجات المتوفّرة عند الاشتراك و50 عرضًا كحدّ أقصى لكل اشتراك.
  • لا تعرض السمة queryProductDetailsAsync() سوى العروض التي يكون المستخدم مؤهلاً لها. إذا حاول المستخدم شراء عرض غير مؤهَّل للاستفادة منه (على سبيل المثال، إذا كان التطبيق يعرض قائمة قديمة بالعروض المؤهَّلة)، يُعلِم Play المستخدم بأنّه غير مؤهَّل ويمكن للمستخدم اختيار شراء الخطة الأساسية بدلاً من ذلك.

بدء مسار الشراء

لبدء طلب شراء من تطبيقك، يُرجى استدعاء الطريقة launchBillingFlow() من سلسلة التعليمات الرئيسية لتطبيقك. تأخذ هذه الطريقة إشارة إلى عنصر BillingFlowParams الذي يحتوي على عنصر ProductDetails ذي الصلة الذي تم الحصول عليه من استدعاء queryProductDetailsAsync(). لإنشاء كائن BillingFlowParams، استخدِم الفئة BillingFlowParams.Builder.

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...;

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
        .setProductDetails(productDetails)
        // For One-time product, "setOfferToken" method shouldn't be called.
        // For subscriptions, to get an offer token, call ProductDetails.subscriptionOfferDetails()
        // for a list of offers that are available to the user
        .setOfferToken(selectedOfferToken)
        .build()
)

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build()

// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Java

// An activity reference from which the billing flow will be launched.
Activity activity = ...;

ImmutableList<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // For one-time products, "setOfferToken" method shouldn't be called.
            // For subscriptions, to get an offer token, call
            // ProductDetails.subscriptionOfferDetails() for a list of offers
            // that are available to the user.
            .setOfferToken(selectedOfferToken)
            .build()
    );

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build();

// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

تعرض الطريقة launchBillingFlow() رمزًا من عدة رموز استجابة مدرجة في BillingClient.BillingResponseCode. تأكد من التحقق من هذه النتيجة للتأكد من عدم وجود أخطاء في بدء تدفق الشراء. تشير قيمة BillingResponseCode في OK إلى بدء إطلاق التطبيق بنجاح.

عند إجراء مكالمة ناجحة مع الرقم launchBillingFlow()، يعرض النظام شاشة الشراء في Google Play. يوضح الشكل 1 شاشة شراء لأحد الاشتراكات:

تعرض شاشة الشراء في Google Play اشتراكًا متاحًا للشراء
الشكل 1. تعرض شاشة الشراء في Google Play اشتراكًا متاحًا للشراء.

يستدعي Google Play onPurchasesUpdated() لعرض نتيجة عملية الشراء على مستمع يستخدم واجهة PurchasesUpdatedListener. يتم تحديد أداة المستمع باستخدام طريقة setListener() عند إعداد برنامجك.

يجب تنفيذ onPurchasesUpdated() لمعالجة رموز الاستجابة المحتملة. يعرض المثال التالي كيفية إلغاء السمة onPurchasesUpdated():

Kotlin

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
   if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
       for (purchase in purchases) {
           handlePurchase(purchase)
       }
   } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
   } else {
       // Handle any other error codes.
   }
}

Java

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK
        && purchases != null) {
        for (Purchase purchase : purchases) {
            handlePurchase(purchase);
        }
    } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
        // Handle an error caused by a user cancelling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

تؤدي عملية الشراء الناجحة إلى إنشاء شاشة نجاح عملية شراء على Google Play على غرار الشكل 2.

شاشة نجاح عملية الشراء في Google Play
الشكل 2. شاشة نجاح عملية الشراء على Google Play

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

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

تحديد سعر مخصّص

إذا كان من الممكن توزيع تطبيقك على المستخدمين في الاتحاد الأوروبي، يُرجى استخدام طريقة setIsOfferPersonalized() لإعلام المستخدمين بأنّه تم تخصيص سعر السلعة من خلال عمليات اتّخاذ القرارات المبرمَجة.

شاشة الشراء على Google Play التي تشير إلى أنّه تم تخصيص السعر للمستخدم
الشكل 3. شاشة الشراء على Google Play التي تشير إلى أنّه تم تخصيص السعر للمستخدم.

يجب الرجوع إلى Art. 6 (1) (أ) الوصول إلى الكمبيوتر المكتبي عن بُعد (CRD) من توجيه حقوق المستهلك (2011/83/EU لتحديد ما إذا كان السعر الذي تقدّمه للمستخدمين مخصّصًا أم لا.

تستخدم setIsOfferPersonalized() إدخالاً منطقيًا. عند true، تتضمّن واجهة مستخدم Play بيان الإفصاح. عند false، تحذِف واجهة المستخدم بيان الإفصاح. القيمة التلقائية هي false.

اطّلِع على مركز مساعدة المستهلكين للحصول على مزيد من المعلومات.

جارٍ معالجة عمليات الشراء

وبعد أن يُكمل المستخدم عملية شراء، يحتاج تطبيقك إلى معالجة عملية الشراء هذه. في معظم الحالات، يتم إرسال إشعار إلى تطبيقك بعمليات الشراء من خلال PurchasesUpdatedListener. ومع ذلك، هناك حالات يتم فيها تعريف تطبيقك على عمليات الشراء من خلال استدعاء BillingClient.queryPurchasesAsync() كما هو موضح في جلب عمليات الشراء.

بالإضافة إلى ذلك، إذا كان لديك عميل إشعارات في الوقت الفعلي خاصة بالمطوّرين في الخلفية الآمنة، يمكنك تسجيل عمليات شراء جديدة من خلال تلقّي إشعار من subscriptionNotification أو oneTimeProductNotification (لعمليات الشراء المعلّقة فقط) لإعلامك بعملية شراء جديدة. بعد تلقّي هذه الإشعارات، يمكنك الاتصال بـ Google Play Developer API للحصول على الحالة الكاملة وتعديل حالة الخلفية.

يجب أن يعالج تطبيقك عملية الشراء على النحو التالي:

  1. أثبِت عملية الشراء.
  2. تقديم المحتوى للمستخدم، والإقرار بتسليمه اختياريًا، ضَع علامة على السلعة للإشارة إلى أنّها مستهلِكة حتى يتمكّن المستخدم من شرائها مرة أخرى.

لتأكيد عملية شراء، تأكَّد أولاً من أنّ حالة الشراء هي PURCHASED. إذا كانت عملية الشراء PENDING، عليك معالجة عملية الشراء على النحو الموضّح في المقالة معالجة المعاملات المعلّقة. بالنسبة إلى عمليات الشراء التي تتلقّاها من onPurchasesUpdated() أو queryPurchasesAsync()، عليك إثبات صحة عملية الشراء قبل أن يمنحها التطبيق الأذونات. لمعرفة كيفية التحقّق من عملية شراء بشكل صحيح، يُرجى الاطّلاع على التحقّق من عمليات الشراء قبل منح الاستحقاقات.

بعد التحقُّق من عملية الشراء، يصبح تطبيقك جاهزًا لمنح الاستحقاق للمستخدم. يمكن التعرّف على حساب المستخدم المرتبط بعملية الشراء بقيمة ProductPurchase.obfuscatedExternalAccountId التي تم إرجاعها من خلال Purchases.products:get لعمليات شراء المنتجات داخل التطبيق و SubscriptionPurchase.obfuscatedExternalAccountId التي تم إرجاعها من خلال Purchases.subscriptions:get للاشتراكات من جهة الخادم، أو obfuscatedAccountId من Purchase.getAccountIdentifiers() من جهة العميل، في حال ضبط الحساب باستخدام setObfuscatedAccountIdPurchases.subscriptions:get للاشتراكات من جهة الخادم.

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

عملية منح الإذن والإقرار بعملية الشراء تعتمد على ما إذا كانت عملية الشراء قابلة للاستهلاك أو غير قابلة للاستهلاك أو اشتراكًا.

المنتجات الاستهلاكية

بالنسبة إلى الأجهزة الاستهلاكية، إذا كان تطبيقك يحتوي على خلفية آمنة، ننصحك باستخدام Purchases.products:consume لتلقّي عمليات الشراء بشكل موثوق. تأكَّد من أنّ عملية الشراء لم يتم استهلاكها من قبل، وذلك من خلال الاطّلاع على consumptionState من نتيجة طلب الرقم Purchases.products:get. إذا كان تطبيقك مخصّصًا للبرنامج فقط بدون خلفية، استخدِم consumeAsync() من Google Play Billing Library. وتستوفي كلتا الطريقتين شرط الإقرار وتشيران إلى أنّ تطبيقك قد منح المستخدم الإذن بالوصول إليه. تؤدّي هذه الطرق أيضًا إلى تمكين تطبيقك من إتاحة المنتج الذي يتم استخدامه مرة واحدة فقط والمطابق للرمز المميّز لشراء الإدخال، لإعادة الشراء. باستخدام consumeAsync()، يجب أيضًا تمرير كائن ينفّذ الواجهة ConsumeResponseListener. يعالج هذا الكائن نتيجة عملية الاستهلاك. يمكنك إلغاء طريقة onConsumeResponse() التي تستدعيها "مكتبة الفوترة في Google Play" عند اكتمال العملية.

يوضّح المثال التالي استهلاك منتج في Google Play Billing Library باستخدام رمز الشراء المميّز المرتبط:

Kotlin

suspend fun handlePurchase(purchase: Purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    val purchase : Purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    val consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build()
    val consumeResult = withContext(Dispatchers.IO) {
        client.consumePurchase(consumeParams)
    }
}

Java

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    Purchase purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Handle the success of the consume operation.
            }
        }
    };

    billingClient.consumeAsync(consumeParams, listener);
}

المنتجات غير الاستهلاكية

للإقرار بعمليات الشراء غير الاستهلاكية، إذا كان تطبيقك يحتوي على خلفية آمنة، ننصحك باستخدام Purchases.products:acknowledge للإقرار بعمليات الشراء بشكل موثوق. تأكَّد من أنّه لم يتم الإقرار سابقًا بعملية الشراء من خلال الاطّلاع على acknowledgementState في نتيجة الاتصال بفريق الدعم على Purchases.products:get.

إذا كان تطبيقك مخصّصًا للعملاء فقط، يُرجى استخدام BillingClient.acknowledgePurchase() من "مكتبة الفوترة في Google Play" في تطبيقك. وقبل الإقرار بعملية الشراء، يجب أن يتحقّق التطبيق مما إذا كان قد تم قبولها أم لا باستخدام طريقة isAcknowledged() في Google Play Billing Library.

يوضّح المثال التالي كيفية تأكيد عملية الشراء باستخدام Google Play Billing Library:

Kotlin

val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ...

suspend fun handlePurchase() {
    if (purchase.purchaseState === PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged) {
            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
            val ackPurchaseResult = withContext(Dispatchers.IO) {
               client.acknowledgePurchase(acknowledgePurchaseParams.build())
            }
        }
     }
}

Java

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

الاشتراكات

يتم التعامل مع الاشتراكات بالطريقة نفسها التي يتم التعامل بها مع المنتجات غير الاستهلاكية. إن أمكن، استخدِم Purchases.subscriptions.acknowledge من واجهة برمجة التطبيقات Google Play Developer API للإقرار بشكل موثوق بعملية الشراء من خلال الخلفية الآمنة. تأكَّد من أنّه لم يتم الإقرار سابقًا بعملية الشراء من خلال الاطّلاع على acknowledgementState في مرجع الشراء من Purchases.subscriptions:get. بخلاف ذلك، يمكنك تأكيد الاشتراك باستخدام BillingClient.acknowledgePurchase() من Google Play Billing Library بعد وضع علامة في المربّع isAcknowledged(). يجب الإقرار بجميع عمليات شراء الاشتراكات الأولية. لا حاجة إلى الإقرار بتجديد الاشتراك. لمزيد من المعلومات حول الحالات التي يجب فيها الإقرار بالاشتراكات، يُرجى الاطّلاع على موضوع بيع الاشتراكات.

جارٍ جلب عمليات الشراء

لا يكفي الاستماع إلى تحديثات الشراء باستخدام PurchasesUpdatedListener لضمان معالجة تطبيقك لجميع عمليات الشراء. من المحتمل ألا يكون تطبيقك على دراية بجميع عمليات الشراء التي أجراها المستخدم. في ما يلي بعض السيناريوهات التي قد يفقد فيها تطبيقك تتبُّع عمليات الشراء أو لا يكون على دراية بها:

  • مشاكل في الشبكة أثناء عملية الشراء: يُجري المستخدم عملية شراء ناجحة ويتلقّى تأكيدًا من Google، ولكن الجهاز يفقد الاتصال بالشبكة قبل تلقّي إشعار بعملية الشراء عبر PurchasesUpdatedListener.
  • أجهزة متعددة: يشتري المستخدم سلعة على جهاز واحد، ثم يتوقّع رؤيتها عند تبديل الأجهزة.
  • التعامل مع عمليات الشراء التي تم إجراؤها خارج التطبيق: يمكن إجراء بعض عمليات الشراء خارج التطبيق، مثل عمليات تحصيل قيمة العروض الترويجية.

للتعامل مع هذه الحالات، تأكَّد من أنّ تطبيقك يطلب البيانات من BillingClient.queryPurchasesAsync() في طريقة onResume() للتأكّد من إتمام جميع عمليات الشراء بنجاح على النحو الموضَّح في معالجة عمليات الشراء.

يوضّح المثال التالي كيفية جلب عمليات شراء الاشتراكات التي أجراها المستخدم. تجدر الإشارة إلى أنّ queryPurchasesAsync() يعرض الاشتراكات النشطة فقط وعمليات الشراء لمرة واحدة غير المستهلكة.

Kotlin

val params = QueryPurchasesParams.newBuilder()
               .setProductType(ProductType.SUBS)

// uses queryPurchasesAsync Kotlin extension function
val purchasesResult = billingClient.queryPurchasesAsync(params.build())

// check purchasesResult.billingResult
// process returned purchasesResult.purchasesList, e.g. display the plans user owns

Java

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
      .setProductType(ProductType.SUBS)
      .build(),
    new PurchasesResponseListener() {
      public void onQueryPurchasesResponse(BillingResult billingResult, List purchases) {
        // check billingResult
        // process returned purchase list, e.g. display the plans user owns

      }
    }
);

جارٍ استرجاع سجلّ الشراء

يعرض "queryPurchaseHistoryAsync()" آخر عملية شراء أجراها المستخدم لكل منتج، حتى إذا انتهت صلاحية عملية الشراء أو تم إلغاؤها أو استهلاكها.

إذا كنت تستخدم إضافات Kotlin، يمكنك استخدام وظيفة الإضافة queryPurchaseHistory().

Kotlin

val params = QueryPurchaseHistoryParams.newBuilder()
               .setProductType(ProductType.SUBS)

// uses queryPurchaseHistory Kotlin extension function
val purchaseHistoryResult = billingClient.queryPurchaseHistory(params.build())

// check purchaseHistoryResult.billingResult
// process returned purchaseHistoryResult.purchaseHistoryRecordList, e.g. display purchase

Java

billingClient.queryPurchaseHistoryAsync(
    QueryPurchaseHistoryParams.newBuilder()
        .setProductType(ProductType.SUBS)
        .build(),
    new PurchaseHistoryResponseListener() {
      public void onPurchaseHistoryResponse(
        BillingResult billingResult, List purchasesHistoryList) {
          // check billingResult
          // process returned purchase history list, e.g. display purchase history
        }
    }
);

معالجة عمليات الشراء التي يتم إجراؤها خارج تطبيقك

يمكن أن تحدث بعض عمليات الشراء، مثل تحصيل قيمة العروض الترويجية، خارج تطبيقك. وعندما يُجري المستخدم عملية شراء خارج التطبيق، يتوقّع أن يعرض التطبيق رسالة داخل التطبيق أو أن يستخدم نوعًا من آلية الإشعارات لإعلام المستخدم بأنّ التطبيق قد تلقّى عملية الشراء وعالجها بشكل صحيح. بعض الآليات المقبولة هي:

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

يُرجى العِلم أنّه من الممكن أن يكون تطبيقك في أي حالة للتعرّف على عملية الشراء. من الممكن أيضًا ألا يتم تثبيت تطبيقك حتى عندما تم إجراء عملية الشراء. يتوقع المستخدمون تلقي عمليات الشراء عند استئناف التطبيق، بغض النظر عن الحالة التي يكون فيها التطبيق.

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

  • أثناء الجزء المتعلق بالحركة في لعبة، حيث قد يؤدي عرض رسالة إلى تشتيت انتباه المستخدم. وفي هذه الحالة، يجب إشعار المستخدم بعد انتهاء الجزء من الإجراء.
  • في المشاهد القصيرة، قد يؤدي عرض رسالة إلى تشتيت انتباه المستخدم. في هذه الحالة، يجب عليك إبلاغ المستخدم بعد انتهاء المشهد.
  • أثناء الدليل التوجيهي الأولي وأجزاء من إعداد المستخدم للّعبة. ننصحك بإعلام المستخدمين الجدد بالمكافأة على الفور بعد فتح اللعبة أو أثناء عملية الإعداد الأولية للمستخدم. ومع ذلك، يُسمح بالانتظار إلى أن يتوفر تسلسل اللعبة الرئيسي لإعلام المستخدم.

ضع المستخدم دائمًا في الاعتبار عند تحديد وقت وكيفية إرسال إشعار للمستخدمين بشأن عمليات الشراء التي تتم خارج التطبيق. وفي أي وقت لا يتلقى المستخدم فيه إشعارًا على الفور، قد يشعر بالارتباك، وقد يتوقف عن استخدام التطبيق، أو يتواصل مع فريق دعم المستخدم، أو يشتكي من ذلك على وسائل التواصل الاجتماعي. ملاحظة: تم تسجيل PurchasesUpdatedListener في سياق تطبيقك للتعامل مع تعديلات الشراء، بما في ذلك عمليات الشراء التي تتم خارج تطبيقك. يعني ذلك أنّه في حال عدم توفّر عملية الطلب، لن يتم إشعار PurchasesUpdatedListener. ولهذا السبب يجب أن يطلب تطبيقك BillingClient.queryPurchasesAsync() بطريقة onResume() كما هو مذكور في جلب عمليات الشراء.

معالجة المعاملات المعلّقة

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

على سبيل المثال، يمكن للمستخدم شراء منتج داخل التطبيق بقيمة PENDING من خلال اختيار طريقة للدفع نقدًا. ويمكن للمستخدم بعد ذلك اختيار متجر فعلي لإكمال المعاملة والحصول على رمز برمجي عبر كل من الإشعار والبريد الإلكتروني. عندما يصل المستخدم إلى المتجر الفعلي، يمكنه استرداد الرمز مع الصرّاف والدفع نقدًا. بعد ذلك، تبلّغ Google أنت والمستخدم باستلام مبلغ مالي. يمكن لتطبيقك بعد ذلك منح إذن الوصول إلى المستخدم.

يجب أن يتيح تطبيقك إجراء المعاملات المعلّقة من خلال الاتصال بالرمز enablePendingPurchases() كجزء من عملية إعداد التطبيق.

عندما يتلقّى تطبيقك عملية شراء جديدة، إمّا من خلال PurchasesUpdatedListener أو نتيجة الاتصال بـ queryPurchasesAsync()، استخدِم طريقة getPurchaseState() لتحديد ما إذا كانت حالة الشراء هي PURCHASED أو PENDING.

إذا كان تطبيقك قيد التشغيل بعد أن يكمل المستخدم عملية الشراء، سيتم استدعاء PurchasesUpdatedListener مرة أخرى، وتغيير اسم PurchaseState الآن إلى PURCHASED. في هذه المرحلة، يمكن لتطبيقك معالجة عملية الشراء باستخدام الطريقة العادية لمعالجة عمليات الشراء لمرة واحدة. يجب أن يطلب التطبيق أيضًا الطلب queryPurchasesAsync() من خلال طريقة onResume() في تطبيقك، وذلك لمعالجة عمليات الشراء التي تم نقلها إلى حالة PURCHASED عندما لم يكن تطبيقك قيد التشغيل.

يمكن لتطبيقك أيضًا استخدام إشعارات في الوقت الفعلي خاصة بالمطوّرين مع عمليات الشراء التي لا تزال في انتظار المراجعة، وذلك من خلال الاستماع إلى OneTimeProductNotifications. عند انتقال عملية الشراء من PENDING إلى PURCHASED، يتلقّى تطبيقك إشعار "ONE_TIME_PRODUCT_PURCHASED". في حال إلغاء عملية الشراء، سيتلقّى تطبيقك إشعارًا بشأن ONE_TIME_PRODUCT_CANCELED. قد يحدث هذا إذا لم يكمل العميل الدفعة في الإطار الزمني المطلوب. عند تلقّي هذه الإشعارات، يمكنك استخدام واجهة برمجة التطبيقات Google Play Developer API التي تتضمّن PENDING حالة لـ Purchases.products.

يمكنك الاطّلاع على الخطوات التفصيلية حول كيفية اختبار هذا السيناريو في صفحة اختبار عمليات الشراء المعلّقة.

التعامل مع عمليات الشراء بكميات متعدّدة

يتوافق Google Play مع الإصدار 4.0 والإصدارات الأحدث من Google Play Billing Library، ويتيح للعملاء شراء أكثر من منتج واحد داخل التطبيق في معاملة واحدة من خلال تحديد كمية المنتج من سلّة الشراء. من المتوقّع أن يتعامل تطبيقك مع عمليات الشراء التي تُجري عمليات شراء متعددة الكميات وأن يمنح الأذونات استنادًا إلى كمية عمليات الشراء المحدّدة.

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

بعد إضافة منطق للتعامل مع عمليات الشراء بكميات متعددة، ستحتاج بعد ذلك إلى تفعيل ميزة "تعدّد الكميات" للمنتج المعني في صفحة إدارة المنتج داخل التطبيق في Google Play Console.

الاستعلام عن إعدادات الفوترة للمستخدم

تحدّد getBillingConfigAsync() البلد الذي يستخدمه المستخدم في Google Play.

يمكنك طلب البحث في إعدادات فوترة المستخدم بعد إنشاء BillingClient. يوضّح مقتطف الرمز التالي كيفية إجراء اتصال بـ getBillingConfigAsync(). يمكنك التعامل مع الاستجابة من خلال تنفيذ سياسة BillingConfigResponseListener يتلقّى هذا المستمع تحديثات لجميع طلبات إعداد الفوترة التي بدأت من تطبيقك.

إذا لم يتضمّن العنصر BillingResult الذي تم عرضه أي أخطاء، يمكنك التحقّق من الحقل countryCode في العنصر BillingConfig لمعرفة بلد المستخدم على Play.

Kotlin

// Use the default GetBillingConfigParams.
val getBillingConfigParams = GetBillingConfigParams.newBuilder().build()
billingClient.getBillingConfigAsync(getBillingConfigParams,
    object : BillingConfigResponseListener {
        override fun onBillingConfigResponse(
            billingResult: BillingResult,
            billingConfig: BillingConfig?
        ) {
            if (billingResult.responseCode == BillingResponseCode.OK
                && billingConfig != null) {
                val countryCode = billingConfig.countryCode
                ...
            } else {
                // TODO: Handle errors
            }
        }
    })

Java

// Use the default GetBillingConfigParams.
GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
billingClient.getBillingConfigAsync(getBillingConfigParams,
    new BillingConfigResponseListener() {
      public void onBillingConfigResponse(
          BillingResult billingResult, BillingConfig billingConfig) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK
            && billingConfig != null) {
            String countryCode = billingConfig.getCountryCode();
            ...
         } else {
            // TODO: Handle errors
        }
      }
    });