تحليل حالات عدم إكمال عمليات شراء المنتجات في "الفوترة في Play"

1. مقدمة

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

الجمهور

هذا الدرس العملي موجَّه لمطوّري تطبيقات Android الذين يستخدمون Play Billing Library (PBL) أو يريدون استخدامها لتحقيق الربح من منتجاتهم التي يتم شراؤها لمرة واحدة.

ما ستتعلمه...

  • كيفية إنشاء منتجات يتم تحصيل سعرها مرة واحدة في Google Play Console
  • كيفية دمج تطبيقك مع ميزة "تشغيل المحتوى أثناء التنقّل"
  • كيفية معالجة عمليات شراء المنتجات الاستهلاكية وغير الاستهلاكية التي يتم تحصيل سعرها مرة واحدة في واجهة Google Play Billing Library
  • كيفية تحليل حالات عدم إكمال عمليات الشراء

المتطلبات...

2. إنشاء نموذج التطبيق

تم تصميم التطبيق النموذجي ليكون تطبيق Android يعمل بكامل وظائفه ويتضمّن رمز المصدر الكامل الذي يعرض الجوانب التالية:

  • دمج التطبيق مع PBL
  • استرداد المنتجات التي يتم تحصيل سعرها مرة واحدة
  • إطلاق مسارات الشراء للمنتجات التي يتم تحصيل سعرها مرة واحدة
  • سيناريوهات الشراء التي تؤدي إلى استجابات الفوترة التالية:
    • BILLING_UNAVAILABLE
    • USER_CANCELLED
    • OK
    • ITEM_ALREADY_OWNED

يوضّح الفيديو التجريبي التالي كيف سيبدو التطبيق النموذجي وكيف سيتصرف بعد نشره وتشغيله.

المتطلّبات الأساسية

قبل إنشاء التطبيق النموذجي ونشره، عليك تنفيذ ما يلي:

إنشاء

الهدف من خطوة الإنشاء هذه هو إنشاء ملف حزمة تطبيق Android مُوقَّع للتطبيق النموذجي.

لإنشاء حِزمة تطبيق Android، اتّبِع الخطوات التالية:

  1. نزِّل تطبيق العيّنة من GitHub.
  2. إنشاء تطبيق العيّنة: قبل الإنشاء، غيِّر اسم حزمة تطبيق العيّنة ثم أنشئه. إذا كانت لديك حِزم لتطبيقات أخرى في Play Console، تأكَّد من أنّ اسم الحزمة الذي تقدّمه للتطبيق النموذجي فريد.

    ملاحظة: لا يؤدي إنشاء التطبيق النموذجي إلا إلى إنشاء ملف APK يمكنك استخدامه للاختبار المحلي. ومع ذلك، لن يؤدي تشغيل التطبيق إلى جلب المنتجات والأسعار لأنّه لم يتم إعداد المنتجات في Play Console، وهو ما ستفعله لاحقًا في هذا الدرس العملي.
  3. أنشِئ حزمة تطبيقات Android موقَّعة.
    1. إنشاء مفتاح تحميل وملف تخزين مفاتيح
    2. توقيع تطبيقك باستخدام مفتاح التحميل
    3. ضبط ميزة "توقيع التطبيق" من Play

الخطوة التالية هي تحميل حِزمة تطبيق Android إلى Google Play Console.

3- إنشاء منتج يتم تحصيل سعره مرة واحدة في Play Console

لإنشاء منتجات تُباع لمرة واحدة في Google Play Console، يجب أن يكون لديك تطبيق في Play Console. أنشئ تطبيقًا في Play Console، ثم حمِّل حزمة التطبيق الموقَّعة التي تم إنشاؤها سابقًا.

إنشاء تطبيق

لإنشاء تطبيق، اتّبِع الخطوات التالية:

  1. سجِّل الدخول إلى Google Play Console باستخدام حساب المطوِّر.
  2. انقر على إنشاء تطبيق. سيؤدي ذلك إلى فتح صفحة إنشاء تطبيق.
  3. أدخِل اسم التطبيق واختَر اللغة التلقائية وتفاصيل أخرى ذات صلة بالتطبيق.
  4. انقر على إنشاء تطبيق، ما يؤدي إلى إنشاء تطبيق في Google Play Console.

يمكنك الآن تحميل حِزمة تطبيق العيّنة الموقَّعة.

تحميل حِزمة التطبيق الموقَّعة

  1. حمِّل حِزمة التطبيق الموقَّعة إلى مسار الاختبار الداخلي في Google Play Console. بعد التحميل فقط، يمكنك ضبط الميزات المتعلّقة بتحقيق الربح في Play Console.
  2. انقر على الاختبار والإصدار > الاختبار > الإصدار الداخلي > إنشاء إصدار جديد.
  3. أدخِل اسم إصدار وحمِّل ملف حِزمة التطبيق الموقَّع.
  4. انقر على التالي، ثمّ انقر على حفظ ونشر.

يمكنك الآن إنشاء منتجاتك التي يتم تحصيل رسومها لمرة واحدة.

إنشاء منتج يتم تحصيل سعره مرة واحدة

لإنشاء منتج يتم تحصيل سعره مرة واحدة، اتّبِع الخطوات التالية:

  1. في Google Play Console، انتقِل من قائمة التنقّل اليمنى إلى تحقيق الربح المادي باستخدام Play > المنتجات > المنتجات التي يتم تحصيل سعرها مرة واحدة.
  2. انقر على إنشاء منتج يتم تحصيل سعره مرة واحدة.
  3. أدخِل تفاصيل المنتج التالية:
    • معرّف المنتج: أدخِل معرّف منتج فريدًا. أدخِل one_time_product_01.
    • (اختياري) العلامات: أضِف علامات ذات صلة.
    • الاسم: أدخِل اسم منتج. مثلاً: Product name
    • الوصف: أدخِل وصفًا للمنتج. مثلاً: Product description
    • (اختياري) إضافة صورة رمز: حمِّل رمزًا يمثّل منتجك.
    ملاحظة: لأغراض هذا الدرس العملي، يمكنك تخطّي ضبط قسم الضريبة والامتثال والبرامج.
  4. انقروا على التالي.
  5. أضِف خيار شراء واضبط مدى توفّره على مستوى منطقة معيّنة. يتطلّب المنتج الذي يتم تحصيل سعره مرة واحدة خيار شراء واحدًا على الأقل يحدّد طريقة توفير المنتج وسعره ومدى توفّره على مستوى منطقة معيّنة. في هذا الدرس العملي، سنضيف خيار شراء المنتج العادي.في قسم خيار الشراء، أدخِل التفاصيل التالية:
    • معرّف خيار الشراء: أدخِل معرّف خيار الشراء. مثلاً: buy
    • نوع عملية الشراء: اختَر شراء.
    • (اختياري) العلامات: أضِف علامات خاصة بخيار الشراء هذا.
    • (اختياري) انقر على خيارات متقدّمة لضبط الخيارات المتقدّمة. لأغراض هذا الدرس البرمجي، يمكنك تخطّي إعداد الخيارات المتقدّمة.
  6. في قسم ضبط الأسعار والتوفّر، انقر على ضبط الأسعار > تعديل الأسعار المجمَّع.
  7. انقر على الخيار البلد أو المنطقة. يؤدي ذلك إلى اختيار جميع المناطق.
  8. انقر على متابعة. سيؤدي ذلك إلى فتح مربّع حوار لإدخال السعر. أدخِل 10 دولار أمريكي ثم انقر على تطبيق.
  9. انقر على حفظ، ثمّ انقر على تفعيل. يؤدي ذلك إلى إنشاء خيار الشراء وتفعيله.

لأغراض هذا الدرس العملي، أنشئ 3 منتجات إضافية تُباع لمرة واحدة باستخدام معرّفات المنتجات التالية:

  • consumable_product_01
  • consumable_product_02
  • consumable_product_03

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

افتح التطبيق النموذجي في Google Play Console، وانتقِل إلى تحقيق الربح المادي باستخدام Google Play > المنتجات > المنتجات التي يتم تحصيل سعرها مرة واحدة. بعد ذلك، انقر على إنشاء منتج يتم تحصيل سعره مرة واحدة وكرِّر الخطوات من 3 إلى 9.

فيديو حول إنشاء منتج يتم تحصيل سعره مرة واحدة

يعرض نموذج الفيديو التالي خطوات إنشاء المنتج الذي يتم تحصيل سعره مرة واحدة والموضّحة سابقًا.

4. الدمج مع PBL

سنوضّح الآن كيفية دمج تطبيقك مع Play Billing Library (PBL). يصف هذا القسم الخطوات العامة لعملية الدمج ويقدّم مقتطفًا من الرمز البرمجي لكل خطوة. يمكنك استخدام هذه المقتطفات كإرشادات لتنفيذ عملية الدمج الفعلية.

لدمج تطبيقك مع ميزة "العطاءات المسبقة"، اتّبِع الخطوات التالية:

  1. أضِف تبعية "مكتبة الفوترة في Play" إلى التطبيق النموذجي.
    dependencies {
    val billing_version = "8.0.0"
    
    implementation("com.android.billingclient:billing-ktx:$billing_version")
    }
    
  2. ابدأ BillingClient. ‫BillingClient هي حزمة تطوير البرامج (SDK) الخاصة بالعميل والموجودة في تطبيقك وتتواصل مع "مكتبة الفوترة في Play". يوضّح مقتطف الرمز البرمجي التالي كيفية تهيئة أداة الفوترة.
    protected BillingClient createBillingClient() {
    return BillingClient.newBuilder(activity)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
        .enableAutoServiceReconnection()
        .build();
    }
    
  3. الربط بخدمة Google Play: يعرض مقتطف الرمز التالي كيفية الربط بخدمة Google Play.
    public void startBillingConnection(ImmutableList<Product> productList) {
    Log.i(TAG, "Product list sent: " + productList);
    Log.i(TAG, "Starting connection");
    billingClient.startConnection(
        new BillingClientStateListener() {
          @Override
          public void onBillingSetupFinished(BillingResult billingResult) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
              // Query product details to get the product details list.
              queryProductDetails(productList);
            } else {
              // BillingClient.enableAutoServiceReconnection() will retry the connection on
              // transient errors automatically.
              // We don't need to retry on terminal errors (e.g., BILLING_UNAVAILABLE,
              // DEVELOPER_ERROR).
              Log.e(TAG, "Billing connection failed: " + billingResult.getDebugMessage());
              Log.e(TAG, "Billing response code: " + billingResult.getResponseCode());
            }
          }
    
          @Override
          public void onBillingServiceDisconnected() {
            Log.e(TAG, "Billing Service connection lost.");
          }
        });
    }
    
  4. استرداد تفاصيل المنتج الذي يتم تحصيل سعره مرة واحدة: بعد دمج تطبيقك مع PBL، عليك استرداد تفاصيل المنتج الذي يتم تحصيل سعره مرة واحدة في تطبيقك. يعرض مقتطف الرمز التالي كيفية استرداد تفاصيل المنتج الذي يتم تحصيل سعره مرة واحدة في تطبيقك.
    private void queryProductDetails(ImmutableList<Product> productList) {
    Log.i(TAG, "Querying products for: " + productList);
    QueryProductDetailsParams queryProductDetailsParams =
        QueryProductDetailsParams.newBuilder().setProductList(productList).build();
    billingClient.queryProductDetailsAsync(
        queryProductDetailsParams,
        new ProductDetailsResponseListener() {
          @Override
          public void onProductDetailsResponse(
              BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) {
            // check billingResult
            Log.i(TAG, "Billing result after querying: " + billingResult.getResponseCode());
            // process returned productDetailsList
            Log.i(
                TAG,
                "Print unfetched products: " + productDetailsResponse.getUnfetchedProductList());
            setupProductDetailsMap(productDetailsResponse.getProductDetailsList());
            billingServiceClientListener.onProductDetailsFetched(productDetailsMap);
          }
        });
    }
    
    سيؤدي جلب ProductDetails إلى عرض استجابة مشابهة لما يلي:
    {
        "productId": "consumable_product_01",
        "type": "inapp",
        "title": "Shadow Coat (Yolo's Realm | Play Samples)",
        "name": "Shadow Coat",
        "description": "A sleek, obsidian coat for stealth and ambushes",
        "skuDetailsToken": "<---skuDetailsToken--->",
        "oneTimePurchaseOfferDetails": {},
        "oneTimePurchaseOfferDetailsList": [
            {
                "priceAmountMicros": 1990000,
                "priceCurrencyCode": "USD",
                "formattedPrice": "$1.99",
                "offerIdToken": "<--offerIdToken-->",
                "purchaseOptionId": "buy",
                "offerTags": []
            }
        ]
    },
    {
        "productId": "consumable_product_02",
        "type": "inapp",
        "title": "Emperor Den (Yolo's Realm | Play Samples)",
        "name": "Emperor Den",
        "description": "A fair lair glowing with molten rock and embers",
        "skuDetailsToken": "<---skuDetailsToken--->",
        "oneTimePurchaseOfferDetails": {},
        "oneTimePurchaseOfferDetailsList": [
            {
                "priceAmountMicros": 2990000,
                "priceCurrencyCode": "USD",
                "formattedPrice": "$2.99",
                "offerIdToken": "<--offerIdToken-->",
                "purchaseOptionId": "buy",
                "offerTags": []
            }
        ]
    }
    
  5. تشغيل مسار الفوترة
    public void launchBillingFlow(String productId) {
    ProductDetails productDetails = productDetailsMap.get(productId);
    if (productDetails == null) {
      Log.e(
          TAG, "Cannot launch billing flow: ProductDetails not found for productId: " + productId);
      billingServiceClientListener.onBillingResponse(
          BillingResponseCode.ITEM_UNAVAILABLE,
          BillingResult.newBuilder().setResponseCode(BillingResponseCode.ITEM_UNAVAILABLE).build());
      return;
    }
    ImmutableList<ProductDetailsParams> productDetailsParamsList =
        ImmutableList.of(
            ProductDetailsParams.newBuilder().setProductDetails(productDetails).build());
    
    BillingFlowParams billingFlowParams =
        BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(productDetailsParamsList)
            .build();
    
    billingClient.launchBillingFlow(activity, billingFlowParams);
    }
    
  6. رصد عمليات الشراء ومعالجتها كجزء من هذه الخطوة، عليك إجراء ما يلي:
    1. تأكيد عملية الشراء
    2. منح المستخدم إذن الوصول إلى المحتوى
    3. إشعار المستخدم
    4. إبلاغ Google بعملية الشراء
    يجب تنفيذ الخطوات أ وب وج في الخلفية، وبالتالي لا يشملها هذا الدرس البرمجي.يعرض المقتطف التالي كيفية إرسال إشعار إلى Google بشأن منتج يُستهلك لمرة واحدة:
    private void handlePurchase(Purchase purchase) {
    // Step 1: Send the purchase to your secure backend to verify the purchase following
    // https://developer.android.com/google/play/billing/security#verify
    
    // Step 2: Update your entitlement storage with the purchase. If purchase is
    // in PENDING state then ensure the entitlement is marked as pending and the
    // user does not receive benefits yet. It is recommended that this step is
    // done on your secure backend and can combine in the API call to your
    // backend in step 1.
    
    // Step 3: Notify the user using appropriate messaging.
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
      for (String product : purchase.getProducts()) {
        Log.d(TAG, product + " purchased successfully! ");
      }
    }
    
    // Step 4: Notify Google the purchase was processed.
    // For one-time products, acknowledge the purchase.
    // This sample app (client-only) uses billingClient.acknowledgePurchase().
    // For consumable one-time products, consume the purchase
    // This sample app (client-only) uses billingClient.consumeAsync()
    // If you have a secure backend, you must acknowledge purchases on your server using the
    // server-side API.
    // See https://developer.android.com/google/play/billing/security#acknowledge
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED && !purchase.isAcknowledged()) {
    
      if (shouldConsume(purchase)) {
        ConsumeParams consumeParams =
            ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
        billingClient.consumeAsync(consumeParams, consumeResponseListener);
    
      } else {
        AcknowledgePurchaseParams acknowledgePurchaseParams =
            AcknowledgePurchaseParams.newBuilder()
                .setPurchaseToken(purchase.getPurchaseToken())
                .build();
        billingClient.acknowledgePurchase(
            acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
      }
     }
    }
    

5- تحليل حالات التوقّف عن الشراء

حتى الآن، ركّزت استجابات "خدمة الفوترة في Play" في هذا الدرس العملي على سيناريوهات محدودة، مثل الاستجابات USER_CANCELLED وBILLING_UNAVAILABLE وOK وITEM_ALREADY_OWNED. ومع ذلك، يمكن أن يعرض نظام الفوترة في Play 13 رمز استجابة مختلفًا يمكن أن تؤدي إلى ظهوره عوامل مختلفة في العالم الحقيقي.

يوضّح هذا القسم أسباب ظهور رمزي الخطأ USER_CANCELLED وBILLING_UNAVAILABLE ويقترح الإجراءات التصحيحية الممكنة التي يمكنك تنفيذها.

رمز خطأ الاستجابة USER_CANCELED

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

الأسباب المحتملة

ما هي الإجراءات التي يمكنك اتّخاذها؟

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

رمز الخطأ BILLING_UNAVAILABLE

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

الأسباب المحتملة

ما هي الإجراءات التي يمكنك اتّخاذها؟

  • تطبيق "متجر Play" على جهاز المستخدم قديم.
  • المستخدم مقيم في بلد لا تتوفّر فيه خدمة Play.
  • المستخدم هو مستخدم في مؤسسة، وقد أوقف مشرف المؤسسة إمكانية إجراء عمليات شراء.
  • يتعذّر على Google Play تحصيل الرسوم من طريقة الدفع التي يستخدمها المستخدم. على سبيل المثال، قد تكون بطاقة ائتمان المستخدم منتهية الصلاحية.
  • مراقبة المؤشرات المتعلقة بمشاكل النظام وفي مناطق معيّنة
  • ننصحك بالانتقال إلى PBL 8 لأنّه يتيح استخدام رمز الاستجابة الفرعية الأكثر تفصيلاً PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS. في حال تلقّي رمز الاستجابة هذا، ننصحك بإرسال إشعار إلى المستخدمين بشأن تعذّر الدفع أو اقتراح طرق دفع بديلة.
  • تم تصميم رمز الاستجابة هذا لإعادة المحاولة، ما يتيح لك تنفيذ استراتيجيات مناسبة لإعادة المحاولة.
    من غير المرجّح أن تساعد عمليات إعادة المحاولة التلقائية في هذه الحالة. ومع ذلك، يمكن أن تساعد إعادة المحاولة يدويًا إذا عالج المستخدم الشرط الذي تسبّب في حدوث المشكلة. على سبيل المثال، إذا حدَّث المستخدم إصدار "متجر Play" إلى إصدار متوافق، قد تنجح إعادة المحاولة يدويًا لتنفيذ العملية الأولية.

    إذا تلقّيت رمز الاستجابة هذا عندما لا يكون المستخدم في جلسة، قد لا يكون إعادة المحاولة أمرًا منطقيًا. عندما تتلقّى الردّ `BILLING_UNAVAILABLE` نتيجةً لمسار الشراء، من المحتمل جدًا أن يكون المستخدم قد تلقّى ملاحظات من Google Play أثناء عملية الشراء وقد يكون على دراية بالمشكلة التي حدثت. في هذه الحالة، يمكنك عرض رسالة خطأ توضّح أنّه حدث خطأ وتقديم زرّ "إعادة المحاولة" لمنح المستخدم خيار إعادة المحاولة يدويًا بعد حلّ المشكلة.

استراتيجيات إعادة المحاولة لرموز أخطاء الاستجابة

تختلف استراتيجيات إعادة المحاولة الفعّالة للأخطاء القابلة للاسترداد من مكتبة Play Billing Library (PBL) استنادًا إلى السياق، مثل تفاعلات المستخدم أثناء الجلسة (مثل أثناء عملية شراء) مقارنةً بالعمليات التي تتم في الخلفية (مثل الاستعلام عن عمليات الشراء عند استئناف التطبيق). من المهم تنفيذ هذه الاستراتيجيات لأنّ بعض قيم BillingResponseCode تشير إلى مشاكل مؤقتة يمكن حلّها من خلال إعادة المحاولة، بينما تكون القيم الأخرى دائمة ولا تتطلّب إعادة المحاولة.

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

للحصول على معلومات تفصيلية عن رموز الاستجابة المحدّدة واستراتيجيات إعادة المحاولة المقترَحة المناسبة لها، اطّلِع على مقالة التعامل مع رموز الاستجابة BillingResult.

6. الخطوات التالية

المستندات المرجعية

7. تهانينا!

تهانينا! لقد تمكّنت بنجاح من التنقّل في Google Play Console لإنشاء منتج جديد يُباع لمرة واحدة، واختبار رموز استجابة الفوترة، وتحليل حالات عدم إتمام عمليات الشراء.

استطلاع

نقدّر ملاحظاتك بشأن هذا الدرس العملي. يُرجى تخصيص بضع دقائق لإكمال الاستطلاع.