کتابخانه صورت‌حساب Google Play را در برنامه خود ادغام کنید

این سند نحوه ادغام کتابخانه پرداخت گوگل پلی در برنامه شما را برای شروع فروش محصولات شرح می‌دهد.

عمر یک خرید

در اینجا یک جریان خرید معمول برای خرید یک‌باره یا اشتراک ارائه شده است.

  1. به کاربر نشان دهید که چه چیزهایی می‌تواند بخرد.
  2. جریان خرید را برای کاربر راه‌اندازی کنید تا خرید را بپذیرد.
  3. خرید را روی سرور خود تأیید کنید.
  4. محتوا را به کاربر بدهید.
  5. تحویل محتوا را تأیید کنید. برای محصولات مصرفی، خرید را مصرف کنید تا کاربر بتواند دوباره آن کالا را خریداری کند.

اشتراک‌ها تا زمان لغو به طور خودکار تمدید می‌شوند. یک اشتراک می‌تواند مراحل زیر را طی کند:

  • فعال : کاربر در وضعیت خوبی قرار دارد و به اشتراک دسترسی دارد.
  • لغو شده : کاربر لغو کرده است اما تا زمان انقضا همچنان دسترسی دارد.
  • در دوره مهلت : کاربر با مشکل پرداخت مواجه شده است، اما همچنان به سیستم دسترسی دارد، در حالی که گوگل در حال امتحان مجدد روش پرداخت است.
  • در انتظار : کاربر در پرداخت با مشکل مواجه شده و دیگر به سیستم دسترسی ندارد، در حالی که گوگل در حال امتحان مجدد روش پرداخت است.
  • متوقف شده : کاربر دسترسی خود را متوقف کرده و تا زمانی که دوباره آن را فعال نکند، دسترسی نخواهد داشت.
  • منقضی شده : کاربر اشتراک را لغو کرده و دسترسی به آن را از دست داده است. در زمان انقضا، کاربر از عضویت انصراف داده شده تلقی می‌شود.

اتصال به گوگل پلی را آغاز کنید

اولین قدم برای ادغام با سیستم پرداخت گوگل پلی، اضافه کردن کتابخانه پرداخت گوگل پلی به برنامه شما و ایجاد یک اتصال اولیه است.

وابستگی کتابخانه پرداخت گوگل پلی را اضافه کنید

همانطور که نشان داده شده است، وابستگی کتابخانه صورتحساب گوگل پلی را به فایل build.gradle برنامه خود اضافه کنید:

شیار

dependencies {
    def billing_version = "8.1.0"

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

کاتلین

dependencies {
    val billing_version = "8.1.0"

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

اگر از کاتلین استفاده می‌کنید، ماژول KTX کتابخانه صورتحساب گوگل پلی شامل افزونه‌ها و پشتیبانی از کوروتین‌های کاتلین است که به شما امکان می‌دهد هنگام استفاده از کتابخانه صورتحساب گوگل پلی، کاتلین ایدیوماتیک بنویسید. برای افزودن این افزونه‌ها به پروژه خود، وابستگی زیر را همانطور که نشان داده شده است به فایل build.gradle برنامه خود اضافه کنید:

شیار

dependencies {
    def billing_version = "8.1.0"

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

کاتلین

dependencies {
    val billing_version = "8.1.0"

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

مقداردهی اولیه یک BillingClient

پس از افزودن وابستگی به کتابخانه صورتحساب گوگل پلی، باید یک نمونه از BillingClient را مقداردهی اولیه کنید. BillingClient رابط اصلی برای ارتباط بین کتابخانه صورتحساب گوگل پلی و بقیه برنامه شما است. BillingClient روش‌های راحتی، چه همزمان و چه غیرهمزمان، را برای بسیاری از عملیات رایج صورتحساب ارائه می‌دهد. به موارد زیر توجه کنید:

  • توصیه می‌شود که همزمان یک اتصال فعال BillingClient باز داشته باشید تا از فراخوانی‌های چندگانه PurchasesUpdatedListener برای یک رویداد واحد جلوگیری شود.
  • توصیه می‌شود هنگام راه‌اندازی یا اجرای برنامه، اتصالی برای BillingClient برقرار کنید تا از پردازش به‌موقع خریدها توسط برنامه اطمینان حاصل شود. این کار را می‌توان با استفاده از ActivityLifecycleCallbacks ثبت‌شده توسط registerActivityLifecycleCallbacks و گوش دادن به onActivityResumed برای راه‌اندازی اولیه اتصال هنگام تشخیص از سرگیری فعالیت، انجام داد. برای جزئیات بیشتر در مورد اینکه چرا باید از این روش برتر پیروی کنید، به بخش پردازش خریدها مراجعه کنید. همچنین به یاد داشته باشید که هنگام بسته شدن برنامه، اتصال را پایان دهید.

برای ایجاد یک BillingClient ، از newBuilder استفاده کنید. می‌توانید هر زمینه‌ای را به newBuilder() ارسال کنید و BillingClient از آن برای دریافت زمینه برنامه استفاده می‌کند. این بدان معناست که نیازی به نگرانی در مورد نشت حافظه ندارید. برای دریافت به‌روزرسانی‌های مربوط به خریدها، باید setListener نیز فراخوانی کنید و یک ارجاع به PurchasesUpdatedListener ارسال کنید. این شنونده، به‌روزرسانی‌های مربوط به همه خریدهای برنامه شما را دریافت می‌کند.

کاتلین

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

private var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   // Configure other settings.
   .build()

جاوا

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)
    // Configure other settings.
    .build();

اتصال به گوگل پلی

پس از ایجاد BillingClient ، باید به Google Play متصل شوید.

برای اتصال به گوگل پلی، startConnection را فراخوانی کنید. فرآیند اتصال ناهمزمان است و شما باید یک BillingClientStateListener پیاده‌سازی کنید تا پس از اتمام راه‌اندازی کلاینت و آماده شدن آن برای ارسال درخواست‌های بیشتر، یک فراخوانی مجدد دریافت کند.

همچنین باید منطق تلاش مجدد را برای مدیریت اتصال‌های از دست رفته به گوگل پلی پیاده‌سازی کنید. برای پیاده‌سازی منطق تلاش مجدد، متد فراخوانی onBillingServiceDisconnected() را بازنویسی کنید و مطمئن شوید که BillingClient قبل از ارسال درخواست‌های بیشتر، متد startConnection() را برای اتصال مجدد به گوگل پلی فراخوانی می‌کند.

مثال زیر نحوه شروع یک اتصال و آزمایش آماده بودن آن برای استفاده را نشان می‌دهد:

کاتلین

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.
    }
})

جاوا

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.
    }
});

برقراری مجدد خودکار اتصال

با معرفی متد enableAutoServiceReconnection() در BillingClient.Builder در نسخه ۸.۰.۰، کتابخانه صورتحساب Play اکنون می‌تواند در صورت برقراری فراخوانی API در حین قطع سرویس، اتصال سرویس را به طور خودکار برقرار کند. این امر می‌تواند منجر به کاهش پاسخ‌های SERVICE_DISCONNECTED شود، زیرا اتصال مجدد قبل از برقراری فراخوانی API به صورت داخلی مدیریت می‌شود.

نحوه فعال کردن اتصال مجدد خودکار

هنگام ساخت یک نمونه BillingClient ، از متد enableAutoServiceReconnection() در BillingClient.Builder برای فعال کردن اتصال مجدد خودکار استفاده کنید.

کاتلین

val billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases()
    .enableAutoServiceReconnection() // Add this line to enable reconnection
    .build()

جاوا

BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases()
    .enableAutoServiceReconnection() // Add this line to enable reconnection
    .build();

نمایش محصولات موجود برای خرید

پس از برقراری اتصال به گوگل پلی، آماده‌اید تا محصولات موجود خود را جستجو کرده و آنها را به کاربران خود نمایش دهید.

جستجوی جزئیات محصول، گامی مهم قبل از نمایش محصولات به کاربران است، زیرا اطلاعات محصول بومی‌سازی‌شده را برمی‌گرداند. برای اشتراک‌ها، مطمئن شوید که نمایش محصول شما از همه خط‌مشی‌های Play پیروی می‌کند .

برای جستجوی جزئیات محصول یکبار مصرف، متد queryProductDetailsAsync را فراخوانی کنید. این متد می‌تواند بر اساس پیکربندی محصول یکبار مصرف شما، چندین پیشنهاد را برگرداند. برای اطلاعات بیشتر، به گزینه‌های خرید چندگانه و پیشنهادات برای محصولات یکبار مصرف مراجعه کنید.

برای مدیریت نتیجه عملیات ناهمزمان، باید یک شنونده (listener) نیز مشخص کنید که رابط ProductDetailsResponseListener را پیاده‌سازی کند. سپس می‌توانید onProductDetailsResponse را که هنگام پایان پرس‌وجو به شنونده اطلاع می‌دهد، همانطور که در مثال زیر نشان داده شده است، بازنویسی کنید:

کاتلین

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

billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
    billingResult,
    queryProductDetailsResult ->
      if (billingResult.getResponseCode() == BillingResponseCode.OK) {
               for (ProductDetails productDetails : queryProductDetailsResult.getProductDetailsList()) {
                 // Process successfully retrieved product details here.
               }

               for (UnfetchedProduct unfetchedProduct : queryproductDetailsResult.getUnfetchedProductList()) {
                 // Handle any unfetched products as appropriate.
               }
            }
}

جاوا

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,
                QueryProductDetailsResult queryProductDetailsResult) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
               for (ProductDetails productDetails : queryProductDetailsResult().getProductDetailsList()) {
                 // Process success retrieved product details here.
               }

               for (UnfetchedProduct unfetchedProduct : queryproductDetailsResult.getUnfetchedProductList()) {
                 // Handle any unfetched products as appropriate.
               }
            }
        }
    }
)

هنگام جستجوی جزئیات محصول، نمونه‌ای از QueryProductDetailsParams را که لیستی از رشته‌های شناسه محصول ایجاد شده در کنسول Google Play را به همراه یک ProductType مشخص می‌کند، ارسال کنید. ProductType می‌تواند برای محصولات یکبار مصرف ProductType.INAPP یا برای اشتراک‌ها ProductType.SUBS باشد.

پرس و جو با افزونه‌های کاتلین

اگر از افزونه‌های کاتلین استفاده می‌کنید ، می‌توانید با فراخوانی تابع افزونه‌ی queryProductDetails() جزئیات محصول یک‌بار مصرف را جستجو کنید.

queryProductDetails() از کوروتین‌های کاتلین استفاده می‌کند، بنابراین نیازی به تعریف یک شنونده جداگانه ندارید. در عوض، تابع تا زمان تکمیل پرس‌وجو به حالت تعلیق در می‌آید و پس از آن می‌توانید نتیجه را پردازش کنید:

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 است. برای ارائه پشتیبانی مناسب برای این سناریو، نحوه استفاده از ویژگی‌های سازگاری معکوس را در راهنمای مهاجرت به Play Billing Library 7 بیاموزید.

نتیجه را پردازش کنید

کتابخانه‌ی پرداخت گوگل پلی (Google Play Billing Library) نتایج پرس‌وجو را در یک شیء به نام QueryProductDetailsResult ذخیره می‌کند. QueryProductDetailsResult شامل List از اشیاء ProductDetails است. سپس می‌توانید متدهای متنوعی را روی هر شیء ProductDetails در لیست فراخوانی کنید تا اطلاعات مربوط به یک محصول یکبار مصرف که با موفقیت واکشی شده است، مانند قیمت یا توضیحات آن، را مشاهده کنید. برای مشاهده‌ی اطلاعات جزئیات محصول موجود، به فهرست متدهای موجود در کلاس ProductDetails مراجعه کنید.

QueryProductDetailsResult همچنین شامل List از اشیاء UnfetchedProduct است. سپس می‌توانید برای دریافت کد وضعیت مربوط به دلیل عدم موفقیت در واکشی، از هر UnfetchedProduct پرس‌وجو کنید. برای مشاهده اطلاعات محصول unfetched موجود، به لیست متدهای موجود در کلاس UnfetchedProduct مراجعه کنید.

قبل از ارائه یک کالا برای فروش، بررسی کنید که کاربر از قبل آن کالا را نداشته باشد. اگر کاربر کالای مصرفی دارد که هنوز در کتابخانه کالای او موجود است، باید قبل از خرید مجدد آن کالا، آن را مصرف کند.

قبل از ارائه اشتراک، مطمئن شوید که کاربر قبلاً مشترک نشده باشد. همچنین به موارد زیر توجه کنید:

  • برای اشتراک‌ها، متد queryProductDetailsAsync() جزئیات محصول اشتراک و حداکثر ۵۰ پیشنهاد واجد شرایط کاربر برای هر اشتراک را برمی‌گرداند. اگر کاربر سعی در خرید یک پیشنهاد غیرمجاز داشته باشد (برای مثال، اگر برنامه لیستی قدیمی از پیشنهادات واجد شرایط را نمایش می‌دهد)، Play به کاربر اطلاع می‌دهد که آنها واجد شرایط نیستند و کاربر می‌تواند به جای آن، طرح پایه را خریداری کند.

  • برای محصولات یک‌بارمصرف، متد queryProductDetailsAsync() فقط پیشنهادهای واجد شرایط کاربر را برمی‌گرداند. اگر کاربر سعی کند پیشنهادی را خریداری کند که واجد شرایط آن نیست (برای مثال، اگر کاربر به محدودیت تعداد خرید رسیده باشد)، Play به کاربر اطلاع می‌دهد که واجد شرایط نیست و کاربر می‌تواند به جای آن، پیشنهاد گزینه خرید آن را خریداری کند.

جریان خرید را راه اندازی کنید

برای شروع درخواست خرید از برنامه خود، متد launchBillingFlow() را از نخ اصلی برنامه خود فراخوانی کنید. این متد یک ارجاع به یک شیء BillingFlowParams دریافت می‌کند که شامل شیء ProductDetails مربوطه است که از فراخوانی queryProductDetailsAsync به دست آمده است. برای ایجاد یک شیء BillingFlowParams ، از کلاس BillingFlowParams.Builder استفاده کنید.

کاتلین

// 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)
        // Get the offer token:
        // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList()
        // for a list of offers that are available to the user.
        // b. For subscriptions, 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)

جاوا

// 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)
            // Get the offer token:
            // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList()
            // for a list of offers that are available to the user.
            // b. For subscriptions, 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() ، سیستم صفحه خرید گوگل پلی را نمایش می‌دهد. شکل 1 صفحه خرید اشتراک را نشان می‌دهد:

صفحه خرید گوگل پلی، اشتراکی را نشان می‌دهد که برای خرید در دسترس است
شکل ۱. صفحه خرید گوگل پلی، اشتراکی را نشان می‌دهد که برای خرید در دسترس است.

گوگل پلی تابع onPurchasesUpdated() را فراخوانی می‌کند تا نتیجه عملیات خرید را به شنونده‌ای که رابط PurchasesUpdatedListener پیاده‌سازی می‌کند، تحویل دهد. این شنونده با استفاده از متد setListener() هنگام مقداردهی اولیه کلاینت شما تعیین می‌شود.

شما باید onPurchasesUpdated() را برای مدیریت کدهای پاسخ احتمالی پیاده‌سازی کنید. مثال زیر نحوه‌ی بازنویسی تابع onPurchasesUpdated() را نشان می‌دهد:

کاتلین

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

جاوا

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

یک خرید موفق، صفحه‌ای مشابه شکل ۲ با عنوان «خرید موفق در گوگل پلی» ایجاد می‌کند.

صفحه موفقیت خرید گوگل پلی
شکل ۲. صفحه موفقیت خرید در گوگل پلی.

یک خرید موفق همچنین یک توکن خرید ایجاد می‌کند که یک شناسه منحصر به فرد است که نشان دهنده کاربر و شناسه محصول برای محصولی است که یک بار خریداری کرده است. برنامه‌های شما می‌توانند توکن خرید را به صورت محلی ذخیره کنند، اگرچه اکیداً توصیه می‌کنیم که توکن را به سرور امن backend خود منتقل کنید تا بتوانید خرید را تأیید کرده و در برابر کلاهبرداری محافظت کنید. این فرآیند در بخش تشخیص و پردازش خریدها بیشتر توضیح داده شده است.

همچنین رسید تراکنش حاوی شناسه سفارش یا شناسه منحصر به فرد تراکنش برای کاربر ایمیل می‌شود. کاربران برای هر خرید یکباره محصول و همچنین برای خرید اولیه اشتراک و تمدیدهای خودکار بعدی، ایمیلی با شناسه سفارش منحصر به فرد دریافت می‌کنند. می‌توانید از شناسه سفارش برای مدیریت بازپرداخت‌ها در کنسول گوگل پلی استفاده کنید.

قیمت شخصی‌سازی‌شده را اعلام کنید

اگر برنامه شما می‌تواند در اتحادیه اروپا بین کاربران توزیع شود، هنگام فراخوانی launchBillingFlow از متد setIsOfferPersonalized() استفاده کنید تا به کاربران اطلاع دهید که قیمت یک کالا با استفاده از تصمیم‌گیری خودکار شخصی‌سازی شده است.

صفحه خرید گوگل پلی که نشان می‌دهد قیمت برای کاربر سفارشی‌سازی شده است.
شکل ۳. صفحه خرید گوگل پلی که نشان می‌دهد قیمت برای کاربر سفارشی‌سازی شده است.

برای تعیین اینکه آیا قیمتی که به کاربران ارائه می‌دهید شخصی‌سازی شده است یا خیر، باید به ماده 6 (1) (ea) CRD از دستورالعمل حقوق مصرف‌کننده 2011/83/EU مراجعه کنید.

setIsOfferPersonalized() یک ورودی بولی می‌گیرد. وقتی true ، رابط کاربری Play شامل افشای اطلاعات می‌شود. وقتی false ، رابط کاربری افشای اطلاعات را حذف می‌کند. مقدار پیش‌فرض آن false است.

برای اطلاعات بیشتر به مرکز پشتیبانی مصرف‌کنندگان مراجعه کنید.

شناسه‌های کاربر را پیوست کنید

وقتی جریان خرید را راه‌اندازی می‌کنید، برنامه شما می‌تواند هر شناسه کاربری که برای کاربری که خرید را انجام می‌دهد با استفاده از obfuscatedAccountId یا obfuscatedProfileId دارید، پیوست کند. یک شناسه نمونه می‌تواند یک نسخه مبهم از ورود کاربر در سیستم شما باشد. تنظیم این پارامترها می‌تواند به گوگل در تشخیص تقلب کمک کند. علاوه بر این، می‌تواند به شما کمک کند تا اطمینان حاصل کنید که خریدها به کاربر مناسب نسبت داده می‌شوند، همانطور که در اعطای حقوق به کاربران بحث شده است.

تشخیص و پردازش خریدها

تشخیص و پردازش خریدی که در این بخش شرح داده شده است، برای همه انواع خریدها، از جمله خریدهای خارج از برنامه مانند بازخریدهای تبلیغاتی، قابل اجرا است.

برنامه شما خریدهای جدید و خریدهای در حال انجام را به یکی از روش‌های زیر تشخیص می‌دهد:

  1. وقتی onPurchasesUpdated در نتیجه فراخوانی launchBillingFlow توسط برنامه شما (همانطور که در بخش قبلی بحث شد) فراخوانی می‌شود، یا اگر برنامه شما با اتصال فعال Billing Library در حال اجرا باشد، زمانی که خریدی خارج از برنامه شما انجام شده یا خریدی در حال انتظار تکمیل شده است. به عنوان مثال، یکی از اعضای خانواده خریدی در حال انتظار را در دستگاه دیگری تأیید می‌کند.
  2. وقتی برنامه شما queryPurchasesAsync را برای پرس و جو از خریدهای کاربر فراخوانی می‌کند.

برای شماره ۱، onPurchasesUpdated به طور خودکار برای خریدهای جدید یا تکمیل‌شده فراخوانی می‌شود، تا زمانی که برنامه شما در حال اجرا باشد و اتصال فعالی به کتابخانه صورتحساب Google Play داشته باشد. اگر برنامه شما در حال اجرا نباشد یا برنامه شما اتصال فعالی به کتابخانه صورتحساب Google Play نداشته باشد، onPurchasesUpdated فراخوانی نخواهد شد. به یاد داشته باشید، توصیه می‌شود برنامه شما تا زمانی که در پیش‌زمینه است، اتصال فعالی را حفظ کند تا به‌روزرسانی‌های خرید به موقع دریافت شود.

برای مورد دوم، باید تابع BillingClient.queryPurchasesAsync() را فراخوانی کنید تا مطمئن شوید برنامه شما تمام خریدها را پردازش می‌کند. توصیه می‌شود این کار را زمانی انجام دهید که برنامه شما با موفقیت با کتابخانه صورتحساب Google Play ارتباط برقرار می‌کند (که توصیه می‌شود هنگام راه‌اندازی برنامه یا آمدن به صحنه، همانطور که در بخش مقداردهی اولیه BillingClient بحث شد، انجام شود. این کار را می‌توان با فراخوانی queryPurchasesAsync هنگام دریافت نتیجه موفقیت‌آمیز به onServiceConnected انجام داد. پیروی از این توصیه برای مدیریت رویدادها و موقعیت‌هایی مانند موارد زیر بسیار مهم است:

  • مشکلات شبکه در حین خرید : یک کاربر می‌تواند خرید موفقی انجام دهد و از گوگل تأیید دریافت کند، اما دستگاه او قبل از اینکه دستگاه و برنامه شما از طریق PurchasesUpdatedListener اعلان خرید را دریافت کند، اتصال به شبکه را از دست می‌دهد.
  • چندین دستگاه : یک کاربر ممکن است یک کالا را در یک دستگاه خریداری کند و سپس انتظار داشته باشد که هنگام تغییر دستگاه، همان کالا را ببیند.
  • مدیریت خریدهای انجام شده خارج از برنامه شما : برخی از خریدها، مانند بازخریدهای تبلیغاتی، می‌توانند خارج از برنامه شما انجام شوند.
  • مدیریت انتقال وضعیت خرید : ممکن است کاربری در حالی که برنامه شما در حال اجرا نیست، پرداخت یک خرید در حال انتظار را تکمیل کند و انتظار داشته باشد هنگام باز کردن برنامه شما، تأیید تکمیل خرید را دریافت کند.
  • اشتراک‌های معلق : یک اشتراک ممکن است در طول چرخه حیات اشتراک به حالت تعلیق درآید. BillingClient.queryPurchasesAsync() فقط در صورتی اشتراک‌های معلق را برمی‌گرداند که پارامتر includeSuspendedSubscriptions روی QueryPurchasesParams.Builder تنظیم شده باشد. اشتراک‌های معلق در PurchasesUpdatedListener برگردانده نمی‌شوند.

به محض اینکه برنامه شما یک خرید جدید یا تکمیل شده را تشخیص دهد، باید:

  • خرید را تأیید کنید.
  • برای خریدهای تکمیل‌شده، به کاربر محتوا اعطا کنید.
  • به کاربر اطلاع دهید.
  • به گوگل اطلاع دهید که برنامه شما خریدهای تکمیل‌شده را پردازش کرده است.

این مراحل در بخش‌های بعدی به تفصیل مورد بحث قرار گرفته و پس از آن بخشی برای خلاصه کردن تمام مراحل ارائه شده است.

خرید را تأیید کنید

برنامه شما باید همیشه قبل از اعطای مزایا به کاربر، مشروعیت خریدها را تأیید کند. این کار را می‌توان با پیروی از دستورالعمل‌های شرح داده شده در «تأیید خریدها قبل از اعطای حق امتیاز» انجام داد. تنها پس از تأیید خرید، برنامه شما باید به پردازش خرید و اعطای حق امتیاز به کاربر ادامه دهد، که در بخش بعدی مورد بحث قرار خواهد گرفت.

اعطای حق دسترسی به کاربر

پس از تأیید خرید توسط برنامه، می‌تواند به اعطای مجوز به کاربر ادامه دهد و به او اطلاع دهد. قبل از اعطای مجوز، مطمئن شوید که برنامه شما وضعیت خرید را در PURCHASED بررسی می‌کند. اگر خرید در حالت «در انتظار» است، برنامه شما باید به کاربر اطلاع دهد که هنوز باید اقداماتی را برای تکمیل خرید انجام دهد تا مجوز اعطا شود. فقط زمانی مجوز اعطا کنید که خرید از «در انتظار» به «موفق» تغییر کند. اطلاعات بیشتر را می‌توانید در بخش «مدیریت تراکنش‌های در انتظار» بیابید.

اگر شناسه‌های کاربری را همانطور که در بخش «شناسه‌های کاربری پیوست‌شده» بحث شد، به خرید پیوست کرده‌اید، می‌توانید آنها را بازیابی کرده و برای نسبت دادن به کاربر صحیح در سیستم خود استفاده کنید. این تکنیک هنگام تطبیق خریدهایی که برنامه شما ممکن است اطلاعات مربوط به کاربر مورد نظر را از دست داده باشد، مفید است. توجه داشته باشید، خریدهایی که خارج از برنامه شما انجام می‌شوند، این شناسه‌ها را تنظیم نمی‌کنند. در این حالت، برنامه شما می‌تواند یا حق دسترسی را به کاربر وارد شده اعطا کند، یا از کاربر بخواهد حساب کاربری مورد نظر خود را انتخاب کند.

برای پیش‌سفارش‌ها ، خرید قبل از رسیدن به زمان انتشار در حالت PENDING (در انتظار خرید) است. خرید پیش‌سفارش در زمان انتشار تکمیل می‌شود و بدون انجام اقدامات اضافی، وضعیت به PURCHASED (خریداری شده) تغییر می‌کند.

به کاربر اطلاع دهید

پس از اعطای مجوز به کاربر، برنامه شما باید اعلانی را برای تأیید خرید موفقیت‌آمیز نشان دهد. به دلیل این اعلان، کاربر در مورد اینکه آیا خرید با موفقیت انجام شده است یا خیر، دچار سردرگمی نمی‌شود، که می‌تواند منجر به توقف استفاده از برنامه شما، تماس با پشتیبانی کاربر یا شکایت از آن در رسانه‌های اجتماعی شود. توجه داشته باشید که برنامه شما ممکن است به‌روزرسانی‌های خرید را در هر زمانی در طول چرخه عمر برنامه شما تشخیص دهد. به عنوان مثال، والدین یک خرید در حال انتظار را در دستگاه دیگری تأیید می‌کنند، در این صورت برنامه شما ممکن است بخواهد اطلاع‌رسانی به کاربر را تا زمان مناسب به تأخیر بیندازد. برخی از نمونه‌هایی که تأخیر در آنها مناسب است عبارتند از:

  • در طول بخش اکشن بازی یا میان‌پرده‌ها، نمایش یک پیام ممکن است حواس کاربر را پرت کند. در این صورت، باید پس از پایان بخش اکشن، به کاربر اطلاع دهید.
  • در طول آموزش اولیه و بخش‌های تنظیم کاربر بازی. به عنوان مثال، ممکن است کاربر قبل از نصب برنامه شما، خریدی را خارج از برنامه شما انجام داده باشد. توصیه می‌کنیم بلافاصله پس از باز کردن بازی یا در طول تنظیم اولیه کاربر، کاربران جدید را از پاداش مطلع کنید. اگر برنامه شما از کاربر می‌خواهد که قبل از اعطای حق به کاربر، یک حساب کاربری ایجاد کند یا وارد سیستم شود، توصیه می‌شود مراحلی را که باید برای دریافت پاداش انجام دهد، به کاربر خود اطلاع دهید. این امر بسیار مهم است زیرا اگر برنامه شما خرید را پردازش نکرده باشد، خریدها پس از ۳ روز بازپرداخت می‌شوند.

هنگام اطلاع‌رسانی به کاربر در مورد خرید، گوگل پلی مکانیسم‌های زیر را توصیه می‌کند:

  • نمایش یک کادر محاوره‌ای درون برنامه‌ای
  • پیام را به یک صندوق پیام درون برنامه‌ای ارسال کنید و به وضوح بیان کنید که پیام جدیدی در صندوق پیام درون برنامه‌ای وجود دارد.
  • از پیام اعلان سیستم عامل استفاده کنید.

این اعلان باید کاربر را در مورد مزایایی که دریافت کرده است، مطلع کند. برای مثال، «شما ۱۰۰ سکه طلا خریداری کردید!». علاوه بر این، اگر خرید در نتیجه مزایای برنامه‌ای مانند Play Pass بوده باشد، برنامه شما این موضوع را به کاربر اطلاع می‌دهد. برای مثال «کالاها دریافت شد! شما همین الان ۱۰۰ جواهر با Play Pass دریافت کردید. ادامه دهید.» هر برنامه ممکن است راهنمایی در مورد متن پیشنهادی برای نمایش به کاربران برای اطلاع‌رسانی مزایا داشته باشد.

به گوگل اطلاع دهید که خرید پردازش شده است

پس از اینکه برنامه شما به کاربر مجوز خرید داد و او را از موفقیت‌آمیز بودن تراکنش مطلع کرد، باید به گوگل اطلاع دهد که خرید با موفقیت انجام شده است. این کار با تأیید خرید انجام می‌شود و باید ظرف سه روز انجام شود تا خرید به طور خودکار بازپرداخت نشود و مجوز لغو نشود . فرآیند تأیید انواع مختلف خرید در بخش‌های بعدی توضیح داده شده است.

محصولات مصرفی

برای اقلام مصرفی، اگر برنامه شما دارای یک backend امن است، توصیه می‌کنیم از Purchases.products:consume برای مصرف مطمئن خریدها استفاده کنید. با بررسی consumptionState از نتیجه فراخوانی Purchases.products:get ، مطمئن شوید که خرید قبلاً مصرف نشده است. اگر برنامه شما فقط برای کلاینت و بدون backend است، از consumeAsync() از کتابخانه صورتحساب Google Play استفاده کنید. هر دو روش، الزام تأیید را برآورده می‌کنند و نشان می‌دهند که برنامه شما به کاربر حق دسترسی داده است. این روش‌ها همچنین برنامه شما را قادر می‌سازند تا محصول یک‌بار مصرف مربوط به توکن خرید ورودی را برای خرید مجدد در دسترس قرار دهد. با consumeAsync() باید شیء‌ای را نیز ارسال کنید که رابط ConsumeResponseListener را پیاده‌سازی می‌کند. این شیء نتیجه عملیات مصرف را مدیریت می‌کند. می‌توانید روش onConsumeResponse() را که کتابخانه صورتحساب Google Play پس از اتمام عملیات فراخوانی می‌کند، نادیده بگیرید.

مثال زیر نحوه‌ی استفاده از یک محصول را با استفاده از کتابخانه‌ی پرداخت گوگل پلی و با استفاده از توکن خرید مربوطه نشان می‌دهد:

کاتلین

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

جاوا

    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);

محصولات غیر مصرفی

برای تأیید خریدهای غیرقابل مصرف، اگر برنامه شما دارای یک backend امن است، توصیه می‌کنیم Purchases.products:acknowledge برای تأیید خریدها به طور قابل اعتماد استفاده کنید. با بررسی وضعیت acknowledgementState از نتیجه فراخوانی Purchases.products:get ، مطمئن شوید که خرید قبلاً تأیید نشده است.

اگر برنامه شما فقط برای کلاینت است، از BillingClient.acknowledgePurchase() از کتابخانه صورتحساب Google Play در برنامه خود استفاده کنید. قبل از تأیید خرید، برنامه شما باید با استفاده از متد isAcknowledged() در کتابخانه صورتحساب Google Play بررسی کند که آیا قبلاً تأیید شده است یا خیر.

مثال زیر نحوه تأیید خرید با استفاده از کتابخانه صورتحساب Google Play را نشان می‌دهد:

کاتلین

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

val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
    .setPurchaseToken(purchase.purchaseToken)
val ackPurchaseResult = withContext(Dispatchers.IO) {
     client.acknowledgePurchase(acknowledgePurchaseParams.build())
}

جاوا

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

AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
 client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);

اشتراک‌ها

اشتراک‌ها مشابه اقلام غیرمصرفی مدیریت می‌شوند. در صورت امکان، از Purchases.subscriptions.acknowledge از API توسعه‌دهندگان Google Play برای تأیید خرید از backend امن خود به طور قابل اعتماد استفاده کنید. با بررسی acknowledgementState در منبع خرید از Purchases.subscriptions:get ، تأیید کنید که خرید قبلاً تأیید نشده است. در غیر این صورت، می‌توانید پس از بررسی isAcknowledged() ، با استفاده از BillingClient.acknowledgePurchase() از کتابخانه صورتحساب Google Play، اشتراک را تأیید کنید. همه خریدهای اولیه اشتراک باید تأیید شوند. تمدید اشتراک نیازی به تأیید ندارد. برای اطلاعات بیشتر در مورد زمان نیاز به تأیید اشتراک، به موضوع فروش اشتراک مراجعه کنید.

خلاصه

قطعه کد زیر خلاصه‌ای از این مراحل را نشان می‌دهد.

کاتلین

fun handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your
    // onPurchasesUpdated.
    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 (delaying
    // notification if needed as discussed above).

    // Step 4: Notify Google the purchase was processed using the steps
    // discussed in the processing purchases section.
}

جاوا

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your
    // onPurchasesUpdated.
    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 (delaying
    // notification if needed as discussed above).

    // Step 4: Notify Google the purchase was processed using the steps
    // discussed in the processing purchases section.
}

برای تأیید اینکه برنامه شما این مراحل را به درستی اجرا کرده است، می‌توانید راهنمای آزمایش را دنبال کنید.

رسیدگی به تراکنش‌های در حال انتظار

گوگل پلی از تراکنش‌های در حال انتظار یا تراکنش‌هایی که نیاز به یک یا چند مرحله اضافی بین شروع خرید توسط کاربر و پردازش روش پرداخت برای خرید دارند، پشتیبانی می‌کند. برنامه شما نباید تا زمانی که گوگل به شما اطلاع ندهد که روش پرداخت کاربر با موفقیت انجام شده است، مجوز این نوع خریدها را اعطا کند.

برای مثال، یک کاربر می‌تواند با انتخاب یک فروشگاه فیزیکی که بعداً با پول نقد پرداخت خواهد کرد، تراکنشی را آغاز کند. کاربر از طریق اعلان و ایمیل، کدی دریافت می‌کند. وقتی کاربر به فروشگاه فیزیکی می‌رسد، می‌تواند کد را از صندوقدار دریافت کرده و با پول نقد پرداخت کند. سپس گوگل به شما و کاربر اطلاع می‌دهد که پرداخت دریافت شده است. سپس برنامه شما می‌تواند به کاربر حق خرید اعطا کند.

فراخوانی enablePendingPurchases() به عنوان بخشی از مقداردهی اولیه BillingClient برای فعال کردن تراکنش‌های در حال انتظار برای برنامه شما. برنامه شما باید تراکنش‌های در حال انتظار را برای محصولات یک‌بار مصرف فعال و پشتیبانی کند. قبل از افزودن پشتیبانی، مطمئن شوید که چرخه عمر خرید برای تراکنش‌های در حال انتظار را درک می‌کنید.

وقتی برنامه شما خرید جدیدی را دریافت می‌کند، چه از طریق PurchasesUpdatedListener و چه در نتیجه فراخوانی queryPurchasesAsync ، از متد getPurchaseState() برای تعیین اینکه آیا وضعیت خرید PURCHASED است یا PENDING استفاده کنید. شما باید فقط زمانی که وضعیت PURCHASED است، مجوز را اعطا کنید.

اگر برنامه شما در حال اجرا باشد و هنگام تکمیل خرید توسط کاربر، اتصال کتابخانه صورتحساب Play شما فعال باشد، PurchasesUpdatedListener شما دوباره فراخوانی می‌شود و PurchaseState اکنون PURCHASED است. در این مرحله، برنامه شما می‌تواند خرید را با استفاده از روش استاندارد شناسایی و پردازش خریدها پردازش کند. برنامه شما همچنین باید queryPurchasesAsync() را در روش onResume() برنامه خود فراخوانی کند تا خریدهایی را که در زمان عدم اجرای برنامه به حالت PURCHASED منتقل شده‌اند، مدیریت کند.

وقتی خرید از PENDING به PURCHASED تغییر می‌کند، کلاینت real_time_developer_notifications شما یک اعلان ONE_TIME_PRODUCT_PURCHASED یا SUBSCRIPTION_PURCHASED » دریافت می‌کند. اگر خرید لغو شود، شما یک اعلان ONE_TIME_PRODUCT_CANCELED یا SUBSCRIPTION_PENDING_PURCHASE_CANCELED دریافت خواهید کرد. این اتفاق زمانی می‌افتد که مشتری شما پرداخت را در بازه زمانی مورد نیاز تکمیل نکند. توجه داشته باشید که همیشه می‌توانید از API توسعه‌دهنده Google Play برای بررسی وضعیت فعلی خرید استفاده کنید.

خریدهای چند مقداری را مدیریت کنید

گوگل پلی که در نسخه‌های ۴.۰ و بالاتر کتابخانه پرداخت گوگل پلی پشتیبانی می‌شود، به مشتریان این امکان را می‌دهد که با مشخص کردن تعداد از سبد خرید، بیش از یک محصول یکبار مصرف را در یک تراکنش خریداری کنند. انتظار می‌رود برنامه شما خریدهای چند مقداری را مدیریت کند و بر اساس تعداد خرید مشخص شده، حق خرید را اعطا کند.

برای پشتیبانی از خریدهای چند مقداری، منطق تأمین برنامه شما باید تعداد کالا را بررسی کند. می‌توانید از یکی از API های زیر به فیلد quantity دسترسی داشته باشید:

بعد از اینکه منطق مدیریت خریدهای چند مقداری را اضافه کردید، باید ویژگی چند مقداری را برای محصول مربوطه در صفحه مدیریت محصول یکبار مصرف در کنسول توسعه‌دهندگان گوگل پلی فعال کنید.

پرس و جو در مورد پیکربندی صورتحساب کاربر

getBillingConfigAsync() کشوری را که کاربر برای گوگل پلی استفاده می‌کند، ارائه می‌دهد.

شما می‌توانید پس از ایجاد یک BillingClient پیکربندی صورتحساب کاربر را بررسی کنید. قطعه کد زیر نحوه فراخوانی getBillingConfigAsync() را شرح می‌دهد. پاسخ را با پیاده‌سازی BillingConfigResponseListener مدیریت کنید. این شنونده، به‌روزرسانی‌ها را برای تمام درخواست‌های پیکربندی صورتحساب که از برنامه شما آغاز می‌شوند، دریافت می‌کند.

اگر BillingResult برگردانده شده حاوی خطایی نباشد، می‌توانید فیلد countryCode را در شیء BillingConfig بررسی کنید تا کشور محل سکونت کاربر را به دست آورید.

کاتلین

// 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
            }
        }
    })

جاوا

// 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
        }
      }
    });

یادآوری‌های رها کردن سبد خرید در صفحه اصلی بازی‌های گوگل پلی (به طور پیش‌فرض فعال است)

For Games developers that monetize through one-time products, one way in which stock-keeping units (SKUs) that are active in Google Play Console can be sold outside of your app is the Cart Abandonment Reminder feature, which nudges users to complete their previously abandoned purchases while browsing the Google Play Store. These purchases happen outside of your app, from the Google Play Games home in the Google Play Store.

This feature is enabled by default to help users pick up where they left off and to help developers maximize sales. However, you can opt your app out of this feature by submitting the Cart Abandonment Reminder feature opt-out form . For best practices on managing SKUs within the Google Play Console, see Create an in-app product .

The following images show the Cart Abandonment Reminder appearing on the Google Play Store:

the Google Play Store screen shows a
    purchase prompt for a previously abandoned purchase
Figure 2. The Google Play Store screen shows a purchase prompt for a previously abandoned purchase.

the Google Play Store screen shows a
    purchase prompt for a previously abandoned purchase
Figure 3. The Google Play Store screen shows a purchase prompt for a previously abandoned purchase.