Правильно ли я выполняю действия по проверке подписки пользователя на Android в приложении?

Я делаю приложение, которое не требует учетной записи/логина пользователя и позволяет пользователю приобретать подписку. Я хочу использовать API разработчика Google Play, чтобы проверить, есть ли у пользователя купленная/активная подписка. Из всей документации я собрал следующие шаги.

Верны ли они, и не могли бы вы ответить на два вопроса в них?

  1. Создайте служебный аккаунт в консоли API Google.
  2. Сохраните предоставленный мне закрытый ключ (где? точно не в моем коде/на устройстве как предложен этот пример кода)
  3. Используйте клиентскую библиотеку API Google для Java, чтобы создать и подписать JWT с помощью закрытого ключа (как ? документы дают мне это, но это не код Java... Что мне с ним делать?)
  4. Создайте запрос токена доступа и получите доступ к API
  5. Теперь приложение может отправлять запрос GET в API, чтобы узнать, не у пользователя есть подписка
  6. Когда срок действия токена доступа истечет, вернитесь к шагу 3.

Кроме того, у меня есть веб-сервис, хотя я ничего не знаю о веб-сервисах или программировании веб-сервисов... Я знаю только достаточно, чтобы осознавать, что его, вероятно, необходимо использовать здесь.

EDIT: эти шаги были неверными. Смотрите мой ответ ниже для правильных шагов. Однако обратите внимание, что это относится только к использованию сервисного аккаунта (поскольку я не хотел, чтобы пользователь явно разрешал доступ к API)


person Kalina    schedule 14.09.2012    source источник


Ответы (7)


Как оказалось, мои действия были неправильными. Мне потребовались недели, чтобы понять это, и, похоже, это больше нигде не задокументировано. Пожалуйста:

  1. Создайте учетную запись веб-приложения в консоли API Google. Поместите любой веб-сайт в качестве «URI перенаправления»; это не имеет значения, так как вы на самом деле не будете его использовать. Вы получите идентификатор клиента и секрет клиента при создании учетной записи.

  2. В браузере на вашем компьютере перейдите к https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=[YOUR REDIRECT URI]&client_id=[YOUR CLIENT ID] и разрешите доступ при появлении запроса.

  3. Посмотрите в адресной строке. В конце URI, который вы ввели изначально, будет ваш токен обновления. Похоже на 1/.... Этот «код» понадобится вам на следующем шаге. Токен обновления не имеет срока действия.

  4. Преобразуйте этот «код» в «токен обновления», перейдя к https://accounts.google.com/o/oauth2/token?client_id=[YOUR CLIENT ID]&client_secret=[YOUR CLIENT SECRET]&code=[CODE FROM PREVIOUS STEP]&grant_type=authorization_code&redirect_uri=[YOUR REDIRECT URI]. Вы можете сохранить полученное значение прямо в своей программе; он никогда не истекает, если он явно не отозван. (этот шаг вставлен @BrianWhite — см. комментарии) Убедитесь, что вы используете POST. (вставлено Гинтасом)

  5. В своем коде отправьте запрос HttpPost на https://accounts.google.com/o/oauth2/token с параметрами BasicNameValuePairs "grant_type","refresh_token", "client_id",[YOUR CLIENT ID], "client_secret",[YOUR CLIENT SECRET], "refresh_token",[YOUR REFRESH TOKEN]. Пример смотрите здесь. Вам нужно будет сделать это в отдельном потоке, возможно, используя AsyncTask. Это вернет JSONObject.

  6. Получите токен доступа из возвращенного JSONObject. Пример смотрите здесь. Вам нужно будет получить строку «access_token». Срок действия токена доступа истекает через 1 час.

  7. В своем коде отправьте запрос HttpGet на https://www.googleapis.com/androidpublisher/v1/applications/[YOUR APP'S PACKAGE NAME]/subscriptions/[THE ID OF YOUR PUBLISHED SUBSCRIPTION FROM YOUR ANDROID DEVELOPER CONSOLE]/purchases/[THE PURCHASE TOKEN THE USER RECEIVES UPON PURCHASING THE SUBSCRIPTION]?accesstoken="[THE ACCESS TOKEN FROM STEP 4]". Пример смотрите здесь.

person Kalina    schedule 25.09.2012
comment
Кстати, вы не должны делать это напрямую из своего приложения, вы должны делать это со своего сервера. Ваше приложение должно напрямую общаться только с вашим сервером. - person Dean Wild; 13.12.2012
comment
@DeanWild Я знаю о рисках, но у меня нет сервера. - person Kalina; 13.12.2012
comment
Достаточно справедливо, просто подумал, что укажу на это, если другие прочитают пост. - person Dean Wild; 13.12.2012
comment
@TheBeatlemaniac Когда я сравниваю ваш ответ с этим документом developers.google.com/android-publisher/authorization между шагами 3 и 4 отсутствует шаг, который заключается в получении токена обновления на основе кода, возвращаемого в адресной строке браузера. Не могли бы вы уточнить это? Кроме того, не могли бы вы уточнить заявление о том, что срок действия токена обновления никогда не истекает, поскольку другие, похоже, думают, что при некоторых обстоятельствах он действительно истекает. (Надеюсь, вы правы в обоих случаях) - person OferR; 01.04.2013
comment
@OferR, на шаге 3 вы получаете токен обновления на основе кода, возвращаемого в адресной строке браузера. Именно об этом говорит этот шаг. Пожалуйста, прочтите его еще раз; Я могу попытаться перефразировать это, если это поможет, но я думаю, что мы говорим об одном и том же здесь. Что касается срока действия токена обновления при некоторых обстоятельствах... Я не слышал об этом, но я полагаю, что это может быть правдой. В моем опыте такого не случалось, но я не знаю, что это за обстоятельства. - person Kalina; 01.04.2013
comment
@TheBeatlemaniac Спасибо за быстрый ответ. Мы выполняем ту же самую процедуру до шага 2. Результат, который я получаю на шаге 2, — это URL-адрес в адресной строке с кодом = xxxxx в конце. Затем я использую этот код с дополнительным запросом HttpPost, который имеет grant_type=authorization_code, и я получаю ответ JSON, из которого я получаю refresh_token, который я использую на шаге 4. Кстати, код, который я получаю (в коде = xxx), начинается с 4 /... Мы видим одно и то же? Еще раз спасибо.. - person OferR; 01.04.2013
comment
@OferR, я считаю, что ты прав. Мне также пришлось пройти еще один шаг после #3, который затем дал мне refresh_token, который можно было использовать на шаге #4: client_id=..., client_secret=..., code=‹код из шага #3›, grant_type=authorization_code, redirect_uri=... - person Brian White; 09.08.2014
comment
Спасибо @Brian White, у меня это хорошо работало в течение последнего года +. - person OferR; 15.08.2014
comment
Я отредактировал ответ, добавив недостающий шаг. Не уверен, что для публикации требуется одобрение другого редактора. - person Brian White; 20.08.2014
comment
@Kalina - я получаю недопустимую строку json запроса. Шаг № 4. Есть ли что-то, что я упускаю? - person Rat-a-tat-a-tat Ratatouille; 18.03.2015
comment
@Kalina - я понял .. я делал запрос на получение вместо публикации. а затем я использовал почтальона для кодирования данных.. (form-url-encode) - person Rat-a-tat-a-tat Ratatouille; 18.03.2015
comment
я получаю сообщение об ошибке ssl при попытке подключиться к Google API .. как мне это решить? А также запрос http 400, когда я пытаюсь поддержать вызов https - person Rat-a-tat-a-tat Ratatouille; 18.03.2015
comment
На шаге 3 я получил 4/........, а следующий шаг возвращает access_token, но 7-й шаг не возвращает информацию о подписчиках. - person er.irfankhan11; 20.11.2015

Если вы похожи на меня и хотите сделать это на PHP, вот процедура, как это сделать... Благодаря ответу Калины мне потребовалось всего три дня, чтобы понять, как это работает :) .

Вот оно:

  1. перейдите в консоль разработчиков Google https://console.developers.google.com/ и создайте веб-приложение. Поместите «developers.google.com/oauthplayground» в качестве «URI перенаправления»; Вы будете использовать его на шаге 2. Вы получите идентификатор клиента и секрет клиента при создании учетной записи. Убедитесь, что у вас добавлен Google Play Android Developer API.

  2. перейдите на платформу Google oauth2 https://developers.google.com/oauthplayground/. Этот замечательный инструмент станет вашим лучшим другом на следующие несколько дней. Теперь перейдите в настройки: убедитесь, что установлено значение Использовать собственные учетные данные OAuth. Только после этого вы сможете ввести свой идентификатор клиента и секрет клиента в форму ниже.

  3. В Игровая площадка Google oauth2 перейдите к шагу 1 Выбор и авторизация API, заполните область в поле ввода https://www.googleapis.com/auth/androidpublisher. Я не смог найти Google Play Android Developer API в списке, возможно, они добавят через некоторое время. Нажмите АВТОРИЗОВАТЬ API. Выполните следующую авторизацию.

  4. На площадке Google oauth2 перейдите к шагу 2 Обмен кодом авторизации для токенов. Если все прошло хорошо, вы увидите код авторизации, начинающийся с /4. Если что-то пошло не так, проверьте сообщение об ошибке справа. Теперь вы нажимаете «обновить токен доступа». Скопируйте токен обновления... он будет начинаться с /1...

  5. Теперь вы всегда можете получить токен доступа! вот как:

    $url ="https://accounts.google.com/o/oauth2/token";
    $fields = array(
       "client_id"=>"{your client id}",
       "client_secret"=>"{your client secret}",
       "refresh_token"=>"{your refresh token 1/.....}",
       "grant_type"=>"refresh_token"
    );
    
    $ch = curl_init($url);
    
    //set the url, number of POST vars, POST data
    curl_setopt($ch, CURLOPT_POST,count($fields));
    curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    //execute post
    $lResponse_json = curl_exec($ch);
    
    //close connection
    curl_close($ch);
    

Теперь у вас есть ТОКЕН ДОСТУПА, ура... JSON будет выглядеть так:

"access_token" : "{the access token}",  "token_type" : "Bearer",  "expires_in" : 3600

Наконец-то вы готовы что-то спросить у Google! Вот как это сделать:

$lAccessToken = "{The access token you got in}" ;
$lPackageNameStr = "{your apps package name com.something.something}";
$lURLStr =  "https://www.googleapis.com/androidpublisher/v1.1/applications/$lPackageNameStr/subscriptions/$pProductIdStr/purchases/$pReceiptStr";

$curl = curl_init($lURLStr);

curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 
$curlheader[0] = "Authorization: Bearer " . $lAccessToken;
curl_setopt($curl, CURLOPT_HTTPHEADER, $curlheader);

$json_response = curl_exec($curl);
curl_close($curl);

$responseObj = json_decode($json_response,true);

Возвращаемый JSON будет содержать две метки времени: initiationTimestampMsec и validUntilTimestampMsec — время действия подписки. Оба являются числом миллисекунд, которое нужно добавить к дате 01.01.1970!

person Rudolfwm    schedule 10.02.2014
comment
Пожалуйста, не забывайте, что второй токен доступа действителен только в течение нескольких часов. После этого просто попросите другой. - person Rudolfwm; 11.08.2014
comment
я получил ошибку типа Array ([error] => Array ( [errors] => Array ( [0] => Array ( [domain] => androidpublisher [причина] => permissionDenied [сообщение] => У текущего пользователя недостаточно прав для выполнения запрошенной операции. ) ) [код] => 401 [сообщение] => У текущего пользователя недостаточно прав для выполнения запрошенной операции. ) ). не могли бы вы мне помочь, пожалуйста - person Paresh Gami; 21.09.2015
comment
В этом коде Что такое $pProductIdStr и $pReceiptStr - person er.irfankhan11; 17.11.2015
comment
@lrfan $pProductIdStr — идентификатор вашего продукта в игровом магазине, а $pReceiptStr — чек, который вы получили при совершении покупки. - person Rudolfwm; 23.11.2015
comment
Мне нужна помощь, пожалуйста, в моем случае и покупка, а не подписка, я уже реализовал обмены в адресе URL, однако результат, который всегда возвращает меня, не найден URL: https://www.googleapis.com/androidpublisher/v2/applications/$lPackageNameStr/purchases/$pProductIdStr/purchases/$pReceiptStr - person Helio Soares Junior; 23.07.2016
comment
у вас правильно установлены $lPackageNameStr, $pProductIdStr и $pReceiptStr? - person Rudolfwm; 27.07.2016
comment
я получаю эту ошибку. { ошибка : неавторизованный_клиент } . Любая идея, что я делаю неправильно здесь? Благодарность - person ; 12.10.2017

Не знаю в 2012, но в 2015 не стоит делать ни один из этих шагов вручную. Мне было очень трудно найти документацию, поэтому я публикую здесь, если это кому-то поможет.

  1. Вы должны запрашивать покупки в приложении только с вашего сервера из соображений безопасности, так как в противном случае вы не можете доверять ни одному из двух концов процесса покупки.

Теперь на стороне сервера (я думаю, вы все равно могли бы использовать тот же код из своего приложения, если вам это абсолютно необходимо), включите клиентскую библиотеку google-api-services-androidpublisher в свой проект (см. https://developers.google.com/api-client-library/java/apis/androidpublisher/v1)

Как вы упомянули, вам нужна учетная запись службы с файлом P12 (клиентская библиотека принимает только файл P12).

Затем следующий код будет аутентифицироваться и красиво получать информацию о покупке:

    HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    JsonFactory jsonFactory = new JacksonFactory();

    List<String> scopes = new ArrayList<String>();
    scopes.add(AndroidPublisherScopes.ANDROIDPUBLISHER);

    Credential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
            .setServiceAccountId(googleServiceAccountId)
            .setServiceAccountPrivateKeyFromP12File(new File(googleServicePrivateKeyPath))
            .setServiceAccountScopes(scopes).build();
    AndroidPublisher publisher = new AndroidPublisher.Builder(httpTransport, jsonFactory, credential).build();
    AndroidPublisher.Purchases purchases = publisher.purchases();
    final Get request = purchases.get(packageName, productId, token);
    final SubscriptionPurchase purchase = request.execute();

    // Do whatever you want with the purchase bean

Информацию об аутентификации клиента Java можно найти здесь: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

person Christophe Fondacci    schedule 13.11.2015

Я могу неправильно понять ваш вопрос, но я не вижу причин, по которым вы должны использовать ссылки, на которые вы ссылаетесь, чтобы заставить работать In-App Billing для приложения Android. Эта страница намного полезнее:

http://developer.android.com/guide/google/play/billing/index.html

Вы можете опробовать демонстрационное приложение, которое они содержат (Подземелья -- http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-download). При этом используются продукты (разовые покупки), а не подписки, но вы должны иметь возможность модифицировать, чтобы проверить, что вы хотите.

Я думаю, что ключом для вас будет метод restoreTransactions, который они предоставляют в примере, чтобы увидеть, есть ли в учетной записи Google Play какие-либо подписки для вашего приложения:

@Override
public void onRestoreTransactionsResponse(RestoreTransactions request, int responseCode) {
    if (responseCode == BillingVars.OK) {                        
        // Update the shared preferences so that we don't perform a RestoreTransactions again.
        // This is also where you could save any existing subscriptions/purchases the user may have.
        SharedPreferences prefs = getSharedPreferences(my_prefs_file, Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = prefs.edit();
        edit.putBoolean(DB_INITIALIZED, true);
        edit.commit();
    } else {
        Log.e(TAG, "RestoreTransactions error: " + responseCode);
    }
}
person Allison    schedule 14.09.2012
comment
Перейдя на страницу подписок по указанной вами ссылке, вы увидите следующее: developer.android.com/guide/google/play/billing/ Использование этого API кажется наиболее безопасным способом проверки подписки пользователя, и для использования этого API вам необходимо авторизовать учетную запись. с OAuth2, который требует шагов, которые я перечислил... Возможно, то, что вы говорите, является лучшим способом? - person Kalina; 15.09.2012

Если у кого-то возникли проблемы с принятыми сообщениями, последний шаг (#7), я обнаружил, что ?access_token= работает вместо ?accessToken=

Жаль, что переполнение стека не позволяет мне сделать этот комментарий прямо в потоке...

person Butch128    schedule 26.07.2017

Поскольку у вас есть веб-служба, которую может вызывать ваше приложение, я бы рекомендовал надежно хранить ваш закрытый ключ на вашем сервере. Вы должны постараться перенести как можно больше вещей внутри приложения на сервисные вызовы, см. эта ссылка. Я реализовал подписку в приложении, но это было до того, как эта часть API вышла. Мне пришлось выполнить собственную регистрацию и проверку безопасности, но похоже, что этот API делает большую часть этого за вас, используя OAuth, хотя похоже, что вы по-прежнему несете ответственность за хранение запроса на подписку/проверку.

Там, где говорится о подписании ваших JWT с помощью существующей библиотеки, они, кажется, предоставляют вам ссылки на библиотеку java, библиотеку Python и библиотеку PHP - это зависит от того, на чем написан ваш веб-сервис или серверный компонент (у меня это C #, поэтому я использую RSACryptoServiceProvider) для проверки подписанных покупок. Они используют объекты JSON для фактической передачи данных.

person John J Smith    schedule 23.09.2012
comment
Спасибо! Однако предыдущий ответ на мой вопрос меня беспокоит. Эти 6 шагов верны, верно? Или я должен просто следовать примеру Dungeons? - person Kalina; 24.09.2012
comment
Из того, что я прочитал (хотя и не использовал до сих пор), я думаю, что ваши 6 шагов верны. Я полагаю, что пример с подземельями был создан раньше, чем некоторые из этих новых функций API, и пример с подземельями не является безопасным. Есть несколько полезных советов по Google I/O, например. youtube.com/watch?v=TnSNCXR9fbY - person John J Smith; 24.09.2012
comment
Вау, отлично. И последний вопрос: мой веб-сервис основан на SOAP, и я знаю, что Google Play API — это REST... Будет ли это проблемой? - person Kalina; 24.09.2012
comment
да. Ваша служба на основе SOAP будет ожидать вызов базового URL-адреса плюс op=MethodName и будет ожидать, что полезная нагрузка будет в формате XML, тогда как эта служба на основе REST будет ожидать вызов URL-адреса, такого как baseurl/method, а полезная нагрузка - это JSON. , Я считаю. Вам, вероятно, придется анализировать ответы и переформатировать их при переходе между вашим сервисом и сервисом Google. - person John J Smith; 24.09.2012

person    schedule
comment
Большая помощь. Просто нужен 1 дополнительный шаг: свяжите проект с Google Play Developer Console, как описано здесь stackoverflow.com/a/25655897/2700303. - person MiguelSlv; 04.02.2017
comment
Спасибо. Это здорово. Вы экономите мое время. Я использую notasecret - как секрет p12. - person FetFrumos; 06.06.2017
comment
Я столкнулся с проблемой и получаю сообщение об ошибке Id = 28, Status = WaitingForActivation, Method = {null}, Result = {Еще не вычислено}. Вы знаете, почему это может быть? - person nadafafif; 30.08.2017
comment
Если вы развернете этот код в Azure, вам нужно использовать X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet в конструкторе X509Certificate2. В противном случае код попытается использовать хранилище сертификатов пользователя, что может быть отклонено. - person Selcuk Sasoglu; 31.08.2017
comment
Это прекрасно работает, но теперь я получаю только 400 Invalid Value в ответ от Google для всех своих токенов. - person Danjoa; 04.01.2018
comment
Мне жаль; Я застрял на этом шаге: чтобы подтвердить покупки в приложении, посетите Cog-›Change Permissions. Где Cog-›Change Permissions? - person Etienne Juneau; 25.09.2018
comment
Нашел. Вернитесь на play.google.com, а не на console.developers.google.com. Также похоже, что пользовательский интерфейс был обновлен. В разделе «Глобальный» я установил 2 флажка: «Просмотр информации о приложении» и «Управление заказами». - person Etienne Juneau; 25.09.2018
comment
Все еще работает в 2020 году, если вы столкнулись с тем, что эта версия Play Developer API больше не доступна, используйте пакет NuGet Google.Apis.AndroidPublisher.v3. - person JPBlanc; 08.04.2020
comment
@JPBlanc Спасибо за комментарий относительно V3! Я обновил пост соответственно. - person RamblinRose; 11.04.2020
comment
Это так безумно... почему мы не можем просто использовать ключ API? - person Meekohi; 09.04.2021
comment
Это мне очень помогло, но я все еще не могу получить: Google.Apis.Requests.RequestError The current user has insufficient permissions to perform the requested operation. [401] несмотря на то, что неоднократно подтверждал, что эта учетная запись службы имеет правильные разрешения. Трудно добиться прогресса, когда Google не дает никакого представления о том, что происходит. Другие говорят, что обновление разрешений может занять до 48 часов. Кто может выполнять какую-либо работу в этой среде? - person Meekohi; 15.04.2021
comment
Для других разочарованных разработчиков: stackoverflow.com/questions/54724015/, похоже, подтверждает, что вам, возможно, придется подождать до 7 дней, чтобы учетная запись службы действительно работала должным образом. - person Meekohi; 15.04.2021