Когда вызов Play Billing Library запускает действие, библиотека возвращает ответ BillingResult
, чтобы информировать разработчиков о результате. Например, если вы используете queryProductDetailsAsync
для получения доступных предложений для пользователя, код ответа либо содержит код OK и предоставляет правильный объект ProductDetails
, либо содержит другой ответ, который указывает причину, по которой объект ProductDetails
не может быть предоставлен.
Не все коды ответов являются ошибками. Справочная страница BillingResponseCode
содержит подробное описание каждого из ответов, обсуждаемых в этом руководстве. Вот некоторые примеры кодов ответов, которые не указывают на ошибки:
-
BillingClient.BillingResponseCode.OK
: действие, инициированное вызовом, было успешно завершено. -
BillingClient.BillingResponseCode.USER_CANCELED
: для действий, которые отображают потоки пользовательского интерфейса Play Store для пользователя, этот ответ указывает, что пользователь покинул эти потоки пользовательского интерфейса, не завершив процесс.
Когда код ответа указывает на ошибку, причина иногда кроется в переходных состояниях, и, таким образом, восстановление возможно. Когда вызов метода Play Billing Library возвращает значение BillingResponseCode
, указывающее на восстанавливаемое состояние, следует повторить вызов. В других случаях состояния не считаются переходными, и, следовательно, повторная попытка не рекомендуется.
Временные ошибки требуют различных стратегий повтора в зависимости от таких факторов, как то, происходит ли ошибка, когда пользователи находятся в сеансе (например, когда пользователь проходит процесс покупки) или ошибка происходит в фоновом режиме (например, когда вы запрашиваете существующие покупки пользователя во время onResume
. Раздел стратегий повтора ниже содержит примеры этих различных стратегий, а раздел ответов Retriable BillingResult
рекомендует, какая стратегия лучше всего подходит для каждого кода ответа.
Помимо кода ответа, некоторые ответы об ошибках включают сообщения для отладки и ведения журнала.
Стратегии повторных попыток
Простая повторная попытка
В ситуациях, когда пользователь находится в сеансе, лучше реализовать простую стратегию повтора, чтобы ошибка как можно меньше нарушала пользовательский опыт. В этом случае мы рекомендуем простую стратегию повтора с максимальным количеством попыток в качестве условия выхода.
В следующем примере демонстрируется простая стратегия повторных попыток для обработки ошибки при установлении соединения BillingClient
:
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
// Initialize the BillingClient.
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
// Establish a connection to Google Play.
fun startBillingConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Billing response OK")
// The BillingClient is ready. You can now query Products Purchases.
} else {
Log.e(TAG, billingResult.debugMessage)
retryBillingServiceConnection()
}
}
override fun onBillingServiceDisconnected() {
Log.e(TAG, "GBPL Service disconnected")
retryBillingServiceConnection()
}
})
}
// Billing connection retry logic. This is a simple max retry pattern
private fun retryBillingServiceConnection() {
val maxTries = 3
var tries = 1
var isConnectionEstablished = false
do {
try {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
isConnectionEstablished = true
Log.d(TAG, "Billing connection retry succeeded.")
} else {
Log.e(
TAG,
"Billing connection retry failed: ${billingResult.debugMessage}"
)
}
}
})
} catch (e: Exception) {
e.message?.let { Log.e(TAG, it) }
} finally {
tries++
}
} while (tries <= maxTries && !isConnectionEstablished)
}
...
}
Экспоненциальная отсрочка повторной попытки
Мы рекомендуем использовать экспоненциальную задержку для операций Play Billing Library, которые происходят в фоновом режиме и не влияют на работу пользователя во время сеанса.
Например, было бы целесообразно реализовать это при подтверждении новых покупок, поскольку эта операция может происходить в фоновом режиме, и подтверждение не обязательно должно происходить в реальном времени, если возникает ошибка.
private fun acknowledge(purchaseToken: String): BillingResult {
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build()
var ackResult = BillingResult()
billingClient.acknowledgePurchase(params) { billingResult ->
ackResult = billingResult
}
return ackResult
}
suspend fun acknowledgePurchase(purchaseToken: String) {
val retryDelayMs = 2000L
val retryFactor = 2
val maxTries = 3
withContext(Dispatchers.IO) {
acknowledge(purchaseToken)
}
AcknowledgePurchaseResponseListener { acknowledgePurchaseResult ->
val playBillingResponseCode =
PlayBillingResponseCode(acknowledgePurchaseResult.responseCode)
when (playBillingResponseCode) {
BillingClient.BillingResponseCode.OK -> {
Log.i(TAG, "Acknowledgement was successful")
}
BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
// This is possibly related to a stale Play cache.
// Querying purchases again.
Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED")
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
{ billingResult, purchaseList ->
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
purchaseList.forEach { purchase ->
acknowledge(purchase.purchaseToken)
}
}
}
}
}
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}"
)
runBlocking {
exponentialRetry(
maxTries = maxTries,
initialDelay = retryDelayMs,
retryFactor = retryFactor
) { acknowledge(purchaseToken) }
}
}
in setOf(
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,
BillingClient.BillingResponseCode.DEVELOPER_ERROR,
BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
) -> {
Log.e(
TAG,
"Acknowledgement failed and cannot be retried --
Response Code: ${acknowledgePurchaseResult.responseCode} --
Debug Message: ${acknowledgePurchaseResult.debugMessage}"
)
throw Exception("Failed to acknowledge the purchase!")
}
}
}
}
private suspend fun <T> exponentialRetry(
maxTries: Int = Int.MAX_VALUE,
initialDelay: Long = Long.MAX_VALUE,
retryFactor: Int = Int.MAX_VALUE,
block: suspend () -> T
): T? {
var currentDelay = initialDelay
var retryAttempt = 1
do {
runCatching {
delay(currentDelay)
block()
}
.onSuccess {
Log.d(TAG, "Retry succeeded")
return@onSuccess;
}
.onFailure { throwable ->
Log.e(
TAG,
"Retry Failed -- Cause: ${throwable.cause} -- Message: ${throwable.message}"
)
}
currentDelay *= retryFactor
retryAttempt++
} while (retryAttempt < maxTries)
return block() // last attempt
}
Повторяемые ответы BillingResult
NETWORK_ERROR (код ошибки 12)
Проблема
Эта ошибка указывает на то, что возникла проблема с сетевым соединением между устройством и системами Play.
Возможное разрешение
Для восстановления используйте простые повторные попытки или экспоненциальную отсрочку, в зависимости от того, какое действие вызвало ошибку.
SERVICE_TIMEOUT (код ошибки -3)
Проблема
Эта ошибка указывает на то, что запрос достиг максимального тайм-аута, прежде чем Google Play сможет ответить. Это может быть вызвано, например, задержкой выполнения действия, запрошенного вызовом Play Billing Library.
Возможное разрешение
Обычно это временная проблема. Повторите запрос, используя либо простую, либо экспоненциальную стратегию отсрочки, в зависимости от того, какое действие вернуло ошибку.
В отличие от SERVICE_DISCONNECTED
ниже, подключение к службе Google Play Billing не разрывается, и вам нужно только повторить попытку выполнить любую операцию Play Billing Library.
SERVICE_DISCONNECTED (код ошибки -1)
Проблема
Эта фатальная ошибка указывает на то, что соединение клиентского приложения с сервисом Google Play Store через BillingClient
было разорвано.
Возможное разрешение
Настоятельно рекомендуется: включить автоматическое повторное подключение к службе
В версии Play Billing Library 8.0.0 появилась функция enableAutoServiceReconnection()
. Настоятельно рекомендуется включить эту функцию при создании BillingClient
. Это позволяет библиотеке автоматически пытаться восстановить соединение при вызове API биллинга во время отключения службы, что значительно снижает частоту возникновения этой ошибки.
Котлин
val billingClient = BillingClient.newBuilder(context)
.setListener(listener)
.enablePendingPurchases()
.enableAutoServiceReconnection() // Enable automatic service reconnection
.build()
Ява
BillingClient billingClient = BillingClient.newBuilder(context)
.setListener(listener)
.enablePendingPurchases()
.enableAutoServiceReconnection() // Enable automatic service reconnection
.build();
Если вы включили автоматическое переподключение услуги
Библиотека Play Billing автоматически попытается переподключиться. Если вы все еще получаете код ответа SERVICE_DISCONNECTED
при выполнении вызова API, это означает, что библиотека не смогла переподключиться после своих автоматических попыток. В этом случае вам следует реализовать логику повтора в своем приложении:
- Для действий, инициированных пользователем (в сеансе): используйте простые повторные попытки вызова API. Основная проблема может быть временной.
- Для фоновых запросов: реализуйте повторные попытки с экспоненциальной задержкой, чтобы избежать перегрузки системы в случае длительного отключения.
Если вы НЕ включили автоматическое переподключение услуги
Чтобы максимально избежать этой ошибки, всегда проверяйте подключение к сервисам Google Play перед выполнением вызовов с помощью Play Billing Library, вызывая BillingClient.isReady()
.
Чтобы попытаться восстановить соединение из SERVICE_DISCONNECTED
, ваше клиентское приложение должно попытаться восстановить соединение с помощью BillingClient.startConnection
.
Как и в случае с SERVICE_TIMEOUT
, используйте простые повторные попытки или экспоненциальную задержку в зависимости от того, какое действие вызвало ошибку.
SERVICE_UNAVAILABLE (код ошибки 2)
Важное примечание:
Начиная с Google Play Billing Library 6.0.0, SERVICE_UNAVAILABLE
больше не возвращается при проблемах с сетью. Он возвращается, когда служба выставления счетов недоступна и устаревшие сценарии SERVICE_TIMEOUT
.
Проблема
Эта временная ошибка указывает на то, что служба Google Play Billing в настоящее время недоступна. В большинстве случаев это означает, что где-то между клиентским устройством и службами Google Play Billing возникла проблема с сетевым подключением.
Возможное разрешение
Обычно это временная проблема. Повторите запрос, используя либо простую, либо экспоненциальную стратегию отсрочки, в зависимости от того, какое действие вернуло ошибку.
В отличие от SERVICE_DISCONNECTED
, подключение к сервису Google Play Billing не разрывается, и вам необходимо повторить попытку выполнения любой операции.
BILLING_UNAVAILABLE (код ошибки 3)
Проблема
Эта ошибка указывает на то, что во время процесса покупки произошла ошибка выставления счета пользователю. Примеры того, когда это может произойти, включают:
- Приложение Play Store на устройстве пользователя устарело.
- Пользователь находится в неподдерживаемой стране.
- Пользователь является корпоративным пользователем, и администратор его предприятия отключил возможность совершать покупки.
- Google Play не может списать платежный метод пользователя. Например, срок действия кредитной карты пользователя мог истечь.
Возможное разрешение
Автоматические повторные попытки вряд ли помогут в этом случае. Однако ручная повторная попытка может помочь, если пользователь устранит условие, вызвавшее проблему. Например, если пользователь обновит свою версию Play Store до поддерживаемой версии, то ручная повторная попытка первоначальной операции может сработать.
Если эта ошибка возникает, когда пользователь не находится в сеансе, повторная попытка может не иметь смысла. Когда вы получаете ошибку BILLING_UNAVAILABLE
в результате процесса покупки, весьма вероятно, что пользователь получил обратную связь от Google Play во время процесса покупки и может знать, что пошло не так. В этом случае вы можете показать сообщение об ошибке, указывающее, что что-то пошло не так, и предложить кнопку «Попробовать еще раз», чтобы дать пользователю возможность вручную повторить попытку после устранения проблемы.
ОШИБКА (код ошибки 6)
Проблема
Это фатальная ошибка, указывающая на внутреннюю проблему в самом Google Play.
Возможное разрешение
Иногда внутренние проблемы Google Play, которые приводят к ERROR
являются временными, и для смягчения можно реализовать повтор с экспоненциальной задержкой. Когда пользователи находятся в сеансе, предпочтительнее простой повтор.
ПУНКТ_УЖЕ_ВЛАДЕЕТ
Проблема
Этот ответ указывает на то, что пользователь Google Play уже владеет подпиской или одноразовым продуктом, который он пытается купить. В большинстве случаев это не временная ошибка, за исключением случаев, когда она вызвана устаревшим кэшем Google Play.
Возможное разрешение
Чтобы избежать этой ошибки, когда причина не в проблеме с кэшем, не предлагайте продукт для покупки, если пользователь уже владеет им. Обязательно проверяйте права пользователя, когда показываете продукты, доступные для покупки, и соответствующим образом фильтруйте то, что пользователь может купить. Когда клиентское приложение получает эту ошибку из-за проблемы с кэшем, ошибка запускает кэш Google Play для обновления последними данными из бэкэнда Play. Повторная попытка после ошибки должна разрешить этот конкретный временный экземпляр в этом случае. Вызовите BillingClient.queryPurchasesAsync()
после получения ITEM_ALREADY_OWNED
, чтобы проверить, приобрел ли пользователь продукт, и, если это не так, реализуйте простую логику повтора для повторной попытки покупки.
ПУНКТ_НЕ_ВЛАДЕЕТ
Проблема
Этот ответ на покупку указывает на то, что пользователь Google Play не владеет подпиской или продуктом одноразовой покупки, который пользователь пытается заменить, подтвердить или использовать. В большинстве случаев это не временная ошибка, за исключением случаев, когда она вызвана устареванием кэша Google Play.
Возможное разрешение
Когда ошибка получена из-за проблемы с кэшем, ошибка запускает обновление кэша Google Play последними данными из бэкэнда Play. Повторная попытка с простой стратегией повтора после ошибки должна разрешить этот конкретный временный экземпляр. Вызовите BillingClient.queryPurchasesAsync()
после получения ITEM_NOT_OWNED
, чтобы проверить, приобрел ли пользователь продукт. Если нет, используйте простую логику повтора, чтобы повторить попытку покупки.
Ответы на невозвратные результаты BillingResult
Эти ошибки невозможно устранить с помощью логики повторных попыток.
ФУНКЦИЯ_НЕ_ПОДДЕРЖИВАЕТСЯ
Проблема
Эта неустранимая ошибка указывает на то, что функция Google Play Billing не поддерживается на устройстве пользователя, вероятно, из-за старой версии Play Store.
Например, возможно, некоторые устройства ваших пользователей не поддерживают обмен сообщениями внутри приложения.
Возможное смягчение
Используйте BillingClient.isFeatureSupported()
для проверки поддержки функций перед вызовом библиотеки Play Billing Library.
when {
billingClient.isReady -> {
if (billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)) {
// use feature
}
}
}
USER_CANCELED
Проблема
Пользователь вышел из пользовательского интерфейса платежного потока.
Возможное разрешение
Это носит исключительно информационный характер и может привести к изящному сбою.
ПУНКТ_НЕДОСТУПЕН
Проблема
Подписка на Google Play Billing или разовый продукт недоступны для покупки для этого пользователя.
Возможное смягчение
Убедитесь, что ваше приложение обновляет сведения о продукте через queryProductDetailsAsync
, как рекомендуется. Учитывайте, как часто ваш каталог продуктов изменяется в конфигурации Play Console, чтобы при необходимости реализовать дополнительные обновления. Пытайтесь продавать в Google Play Billing только те продукты, которые возвращают правильную информацию через queryProductDetailsAsync
. Проверьте конфигурацию соответствия продукта на наличие несоответствий. Например, вы можете запрашивать продукт, который доступен только для региона, отличного от того, который пользователь пытается купить. Чтобы быть доступным для покупки, продукт должен быть активным, его приложение должно быть опубликовано, а его приложение должно быть доступно в стране пользователя.
Иногда, особенно во время тестирования, в конфигурации продукта все правильно, но пользователи все равно видят эту ошибку. Это может быть связано с задержкой распространения сведений о продукте на серверах Google. Повторите попытку позже.
DEVELOPER_ERROR
Проблема
Это фатальная ошибка, которая указывает на то, что вы неправильно используете API. Например, предоставление неверных параметров BillingClient.launchBillingFlow
может вызвать эту ошибку.
Возможное разрешение
Убедитесь, что вы правильно используете различные вызовы Play Billing Library. Также проверьте сообщение об отладке для получения дополнительной информации об ошибке.