Когда вызов библиотеки Play Платежей запускает действие, библиотека возвращает ответ BillingResult, чтобы сообщить разработчикам о результате. Например, если вы используете
queryProductDetailsAsync
для получения доступных предложений для пользователя, код ответа либо содержит код OK и предоставляет правильный объект ProductDetails, либо содержит другой ответ, указывающий причину, по которой объект ProductDetails не может быть предоставлен.
Не все коды ответов указывают на ошибки. На BillingResponseCodeсправочной странице приведено подробное описание каждого ответа, упомянутого в этом руководстве.
Вот несколько примеров кодов ответов, которые не указывают на ошибки:
BillingClient.BillingResponseCode.OK: действие, вызванное звонком, было успешно выполнено.BillingClient.BillingResponseCode.USER_CANCELED: для действий, которые показывают пользователю интерфейс Google Play, этот ответ означает, что пользователь покинул интерфейс, не завершив процесс.
Если код ответа указывает на ошибку, ее причина иногда связана с временными условиями, поэтому восстановление возможно. Если при вызове метода библиотеки Play Платежей возвращается значение BillingResponseCode, указывающее на восстанавливаемое состояние, следует повторить вызов. В других случаях условия не считаются временными, поэтому повторная попытка не рекомендуется.
Для временных ошибок требуются разные стратегии повторных попыток в зависимости от таких факторов, как то, произошла ли ошибка, когда пользователи были в сеансе (например, когда пользователь совершал процесс покупки) или в фоновом режиме (например, когда вы запрашивали существующие покупки пользователя во время onResume).
В разделе о стратегиях повторных попыток ниже приведены примеры разных стратегий, а в BillingResultразделе Ответы, которые можно повторить рассказывается, какая стратегия лучше всего подходит для каждого кода ответа.
Помимо кода ответа, некоторые ответы об ошибках содержат сообщения для отладки и ведения журналов.
Стратегии повторных попыток
Простая повторная попытка
Если пользователь находится в сеансе, лучше реализовать простую стратегию повторных попыток, чтобы ошибка как можно меньше мешала ему. В этом случае мы рекомендуем использовать простую стратегию повторных попыток с максимальным количеством попыток в качестве условия выхода.
В следующем примере показана простая стратегия повторных попыток для обработки ошибки при установке подключения BillingClient:
// Initialize the BillingClient. private val billingClient = BillingClient.newBuilder(context) .setListener(this) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .build() private val coroutineScope = kotlinx.coroutines.CoroutineScope( kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate ) private var connectionJob: kotlinx.coroutines.Job? = null // Establish a connection to Google Play. fun startBillingConnection() { connectionJob?.cancel() connectionJob = coroutineScope.launch { connectWithRetry() } } // Suspended helper to perform a single connection attempt private suspend fun connectBilling(): BillingResult = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (continuation.isActive) { continuation.resume(billingResult) } } override fun onBillingServiceDisconnected() { Log.e(TAG, "Google Play Billing Service disconnected") if (continuation.isActive) { continuation.resume( BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) .setDebugMessage("Service disconnected during connection setup") .build() ) } else { startBillingConnection() } } }) } // Billing connection retry logic. This is a simple max retry pattern private suspend fun connectWithRetry() { val maxTries = 3 var tries = 1 var isConnectionEstablished = false while (tries <= maxTries && !isConnectionEstablished) { val billingResult = connectBilling() if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { isConnectionEstablished = true Log.d(TAG, "Billing response OK") } else { Log.e(TAG, "Billing connection retry failed: ${billingResult.debugMessage}") tries++ if (tries <= maxTries) { delay(2000L) // Wait 2 seconds before retrying } } } } fun cleanUp() { coroutineScope.cancel() } // ...
Повторная попытка с экспоненциальной выдержкой
Мы рекомендуем использовать экспоненциальную выдержку для операций библиотеки Play Платежей, которые выполняются в фоновом режиме и не влияют на удобство использования, пока пользователь находится в сеансе.
Например, это можно сделать при подтверждении новых покупок, поскольку эта операция может выполняться в фоновом режиме и не требует немедленного выполнения в случае ошибки.
private suspend fun acknowledge(purchaseToken: String): BillingResult = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> val params = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchaseToken) .build() billingClient.acknowledgePurchase(params) { billingResult -> continuation.resumeWith(Result.success(billingResult)) } } private suspend fun queryPurchases(productType: String): Pair<BillingResult, List<Purchase>> = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> val params = QueryPurchasesParams.newBuilder() .setProductType(productType) .build() billingClient.queryPurchasesAsync(params) { billingResult, purchaseList -> continuation.resumeWith(Result.success(Pair(billingResult, purchaseList))) } } suspend fun acknowledgePurchase(purchaseToken: String) { val retryDelayMs = 2000L val retryFactor = 2 val maxTries = 3 var tries = 1 var currentDelay = retryDelayMs var acknowledgePurchaseResult: BillingResult do { acknowledgePurchaseResult = acknowledge(purchaseToken) val playBillingResponseCode = acknowledgePurchaseResult.responseCode when (playBillingResponseCode) { BillingClient.BillingResponseCode.OK -> { Log.i(TAG, "Acknowledgement was successful") return } BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> { Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED") val (billingResult, purchaseList) = queryPurchases(BillingClient.ProductType.SUBS) if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { purchaseList.forEach { purchase -> acknowledge(purchase.purchaseToken) } } return } in setOf( BillingClient.BillingResponseCode.ERROR, BillingClient.BillingResponseCode.SERVICE_DISCONNECTED, BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE, ) -> { Log.d( TAG, "Acknowledgement failed, but can be retried -- " + "Response Code: ${acknowledgePurchaseResult.responseCode} -- " + "Debug Message: ${acknowledgePurchaseResult.debugMessage}" ) if (tries < maxTries) { delay(currentDelay) currentDelay *= retryFactor tries++ } else { break } } else -> { Log.e( TAG, "Acknowledgement failed and cannot be retried -- " + "Response Code: ${acknowledgePurchaseResult.responseCode} -- " + "Debug Message: ${acknowledgePurchaseResult.debugMessage}" ) throw Exception("Failed to acknowledge the purchase!") } } } while (tries <= maxTries) throw Exception("Failed to acknowledge the purchase after $maxTries attempts!") }
Ответы BillingResult, которые можно повторить
NETWORK_ERROR (код ошибки 12)
Проблема
Эта ошибка означает, что возникла проблема с сетевым подключением между устройством и системами Google Play.
Возможные решения
Чтобы устранить проблему, используйте простые повторные попытки или экспоненциальную выдержку в зависимости от того, какое действие вызвало ошибку.
SERVICE_TIMEOUT (код ошибки -3)
Проблема
Эта ошибка означает, что время ожидания ответа от Google Play истекло. Например, это может быть связано с задержкой выполнения действия, запрошенного вызовом библиотеки Play Платежей.
Возможные решения
Как правило, это временная проблема. Повторите запрос, используя простую или экспоненциальную стратегию выдержки в зависимости от того, какое действие вернуло ошибку.
В отличие от SERVICE_DISCONNECTED, описанного ниже, подключение к сервису Google Play Платежей не прерывается, и вам нужно только повторить операцию, которую вы пытались выполнить с помощью библиотеки Google Play Платежей.
SERVICE_DISCONNECTED (код ошибки -1)
Проблема
Эта критическая ошибка означает, что подключение клиентского приложения к сервису Google Play через BillingClient было разорвано.
Возможные решения
Рекомендуется включить автоматическое восстановление подключения к сервису
В библиотеке Play Платежей версии 8.0.0 появилась функция enableAutoServiceReconnection().
Настоятельно рекомендуем включить эту функцию при создании BillingClient. Это позволяет библиотеке автоматически пытаться восстановить подключение при вызове API для выставления счетов, когда сервис отключен, что значительно снижает вероятность возникновения этой ошибки.
Kotlin
val billingClient = BillingClient.newBuilder(context) .setListener(listener) .enablePendingPurchases( PendingPurchasesParams.newBuilder().enableOneTimeProducts().build() ) .enableAutoServiceReconnection() // Enable automatic service reconnection .build()
Java
BillingClient billingClient = BillingClient.newBuilder(context)
.setListener(listener)
.enablePendingPurchases()
.enableAutoServiceReconnection() // Enable automatic service reconnection
.build();
Если вы включили автоматическое повторное подключение сервиса
Библиотека Play Платежей автоматически попытается восстановить подключение. Если при вызове API вы по-прежнему получаете код ответа SERVICE_DISCONNECTED, это означает, что библиотеке не удалось восстановить подключение после автоматических попыток.
В этом случае вам следует реализовать в приложении логику повторных попыток:
- Для действий, инициированных пользователем (в сеансе): используйте простые повторные попытки вызова API. Возможно, проблема временная.
- Для фоновых запросов: реализуйте повторные попытки с экспоненциальной выдержкой, чтобы избежать перегрузки системы в случае длительного отключения.
Если вы НЕ включили автоматическое восстановление подключения к сервису
Чтобы избежать этой ошибки, всегда проверяйте подключение к сервисам Google Play перед вызовом функций библиотеки Play Платежей, используя метод BillingClient.isReady().
Чтобы восстановить подключение после ошибки SERVICE_DISCONNECTED, клиентское приложение должно попытаться установить его заново, используя BillingClient.startConnection.
Как и в случае с SERVICE_TIMEOUT, используйте простые повторные попытки или экспоненциальную выдержку в зависимости от того, какое действие вызвало ошибку.
SERVICE_UNAVAILABLE (код ошибки 2)
Важно!
Начиная с версии 6.0.0 библиотеки Google Play Платежей, SERVICE_UNAVAILABLE больше не возвращается при проблемах с сетью. Возвращается, когда сервис оплаты недоступен, а также в устаревших сценариях использования SERVICE_TIMEOUT.
Проблема
Эта временная ошибка означает, что сервис Google Play Платежей в данный момент недоступен. В большинстве случаев это означает, что возникла проблема с сетевым подключением между клиентским устройством и сервисами Google Play Платежи.
Возможные решения
Как правило, это временная проблема. Повторите запрос, используя простую или экспоненциальную стратегию выдержки в зависимости от того, какое действие вернуло ошибку.
В отличие от SERVICE_DISCONNECTED
связь с платежной системой Google Play не прерывается, и вам нужно повторить попытку выполнить операцию.
BILLING_UNAVAILABLE (код ошибки 3)
Проблема
Это означает, что при покупке произошла ошибка оплаты. Вот несколько примеров:
- На устройстве пользователя установлена устаревшая версия приложения Google Play.
- Пользователь находится в стране, где сервис недоступен.
- Пользователь является корпоративным клиентом, и администратор его организации запретил пользователям совершать покупки.
- Google Play не может списать средства с помощью указанного пользователем способа оплаты. Например, у пользователя могла истечь кредитная карта.
- Приложение Google Play заблокировано системой (например, в детском режиме, настроенном производителем устройства). В этом случае в
BillingResultбудет сообщение об ошибке Play Store is blocked (Магазин Google Play заблокирован).
Возможные решения
Автоматические повторные попытки в этом случае вряд ли помогут. Однако повторная попытка вручную может помочь, если пользователь устранит причину проблемы. Например, если пользователь обновит Google Play до поддерживаемой версии, то повторная попытка выполнить исходную операцию может сработать.
Если эта ошибка возникает, когда пользователь не вошел в аккаунт, повторная попытка может быть бесполезной. Если в процессе покупки возникает ошибка
BILLING_UNAVAILABLE, скорее всего, пользователь получил от Google Play сообщение о том, что пошло не так. В этом случае можно показать сообщение об ошибке и предложить пользователю нажать кнопку Повторить после того, как он устранит проблему.
ОШИБКА (код ошибки 6)
Проблема
Это критическая ошибка, указывающая на внутреннюю проблему с Google Play.
Возможные решения
Иногда внутренние проблемы Google Play, приводящие к ошибке ERROR, носят временный характер, и для их устранения можно реализовать повторную попытку с экспоненциальным откладыванием. Если пользователь находится в сеансе, лучше просто повторить попытку.
ITEM_ALREADY_OWNED
Проблема
Этот ответ означает, что у пользователя Google Play уже есть подписка или продукт с разовой покупкой, который он пытается приобрести. В большинстве случаев это не временная ошибка, за исключением ситуаций, когда она вызвана устаревшим кешем Google Play.
Возможные решения
Чтобы избежать этой ошибки, когда ее причина не связана с кешем, не предлагайте пользователю купить продукт, если он уже приобрел его. При показе товаров, доступных для покупки, проверяйте права пользователя и фильтруйте товары в соответствии с ними.
Когда клиентское приложение получает эту ошибку из-за проблемы с кешем, она запускает обновление кеша Google Play с последними данными из серверной части сервиса.
Повторная попытка после ошибки должна устранить эту временную проблему. Вызовите BillingClient.queryPurchasesAsync() после получения ITEM_ALREADY_OWNED, чтобы проверить, приобрел ли пользователь продукт. Если нет, реализуйте простую логику повторных попыток, чтобы повторить покупку.
ITEM_NOT_OWNED
Проблема
Этот ответ на запрос покупки указывает, что у пользователя Google Play нет подписки или разовой покупки, которую он пытается заменить, подтвердить или использовать. В большинстве случаев это не временная ошибка, за исключением ситуаций, когда она вызвана устаревшим кешем Google Play.
Возможные решения
Если ошибка связана с кешем, она приводит к тому, что кеш Google Play обновляется с использованием последних данных из серверной части сервиса. Повторная попытка с простой стратегией после ошибки должна устранить эту временную проблему. Вызывайте BillingClient.queryPurchasesAsync() после получения ITEM_NOT_OWNED, чтобы проверить, приобрел ли пользователь товар. Если нет, используйте простую логику повторных попыток, чтобы повторить покупку.
Ответы BillingResult, которые нельзя повторно запросить
Восстановить работу после таких ошибок с помощью логики повторных попыток не получится.
FEATURE_NOT_SUPPORTED
Проблема
Эта ошибка, которую нельзя устранить, указывает на то, что функция Google Play Платежей не поддерживается на устройстве пользователя, скорее всего из-за устаревшей версии Google Play.
Например, некоторые устройства пользователей могут не поддерживать сообщения в приложении.
Возможные меры по устранению проблемы
Используйте BillingClient.isFeatureSupported(), чтобы проверить поддержку функции, прежде чем вызывать библиотеку платежей Google Play.
when { billingClient.isReady -> { val billingResult = billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING) if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { // use Feature } } }
USER_CANCELED
Проблема
Пользователь покинул интерфейс сценария оплаты.
Возможные решения
Это информационное сообщение, которое можно проигнорировать.
ITEM_UNAVAILABLE
Проблема
Подписка Google Play Платежей или продукт для разовой покупки недоступны для этого пользователя.
Возможные меры по устранению проблемы
Убедитесь, что приложение обновляет сведения о товарах с помощью метода queryProductDetailsAsync, как рекомендуется. Учитывайте, как часто меняется каталог товаров в Play Console, чтобы при необходимости реализовать дополнительные обновления.
Продавайте через Google Play Платежи только те товары, которые возвращают правильную информацию через queryProductDetailsAsync.
Проверьте настройки соответствия требованиям к товарам на наличие несоответствий.
Например, вы можете искать товар, который доступен только в регионе, отличном от того, в котором находится пользователь.
Доступным для продажи будет только активный контент в опубликованном приложении, которое не заблокировано в стране пользователя.
Иногда, особенно во время тестирования, все настройки продукта верны, но пользователи все равно видят эту ошибку. Это может быть связано с задержкой распространения сведений о товаре на серверах Google. Повторите попытку позже.
DEVELOPER_ERROR
Проблема
Это критическая ошибка, которая указывает на неправильное использование API.
Например, эта ошибка может возникнуть, если передать неправильные параметры в функцию BillingClient.launchBillingFlow.
Возможные решения
Убедитесь, что вы правильно используете различные вызовы библиотеки Play Платежей. Также изучите сообщение об ошибке, чтобы получить дополнительную информацию.