Невозможно получить информацию о подписке из Google Play Android Developer API
Я не собираюсь использовать клиентскую библиотеку API Google для Java для получения информации о подписках пользователя, приобретенных в моем приложении для Android. Вот как я сейчас делаю:
HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(GOOGLE_CLIENT_MAIL)
.setServiceAccountScopes("https://www.googleapis.com/auth/androidpublisher")
.setServiceAccountPrivateKeyFromP12File(new File(GOOGLE_KEY_FILE_PATH))
.build();
Androidpublisher publisher = new Androidpublisher.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).
setApplicationName(GOOGLE_PRODUCT_NAME).
build();
Androidpublisher.Purchases purchases = publisher.purchases();
Get get = purchases.get("XXXXX", subscriptionId, token);
SubscriptionPurchase subscripcion = get.execute(); //Exception returned here
GOOGLE_CLIENT_MAIL
адрес электронной почты из API Access из консоли Google.GOOGLE_KEY_FILE_PATH
это файл p12, загруженный из API Access.GOOGLE_PRODUCT_NAME
это название продукта из информации о брендинге.
В консоли Google APIS включена служба "Google Play Android Developer API".
Что я получаю это:
{
"code" : 401,
"errors" : [ {
"domain" : "androidpublisher",
"message" : "This developer account does not own the application.",
"reason" : "developerDoesNotOwnApplication"
} ],
"message" : "This developer account does not own the application."
}
Я очень ценю вашу помощь в этом вопросе...
6 ответов
Я получил это работает! Шаги, которые я выполнил:
необходимое условие
Перед запуском нам нужно сгенерировать токен обновления. Для этого сначала нужно создать консольный проект API:
- Перейдите в консоль API и войдите в свою учетную запись разработчика Android (ту же учетную запись, которая использовалась в консоли разработчика Android для загрузки APK).
- Выберите Создать проект.
- Перейдите в раздел Сервисы в левой навигационной панели.
- Включите Google Play Android Developer API.
- Принять Условия обслуживания.
- Перейдите к Доступу API в левой навигационной панели.
- Выберите Создать идентификатор клиента OAuth 2.0:
- На первой странице вам нужно будет указать название продукта, но логотип не требуется.
- На второй странице выберите веб-приложение и установите URI перенаправления и происхождение Javascript. Мы будем использовать его позже URI перенаправления.
- Выберите Создать идентификатор клиента. Имейте в виду идентификатор клиента и секрет клиента, мы будем использовать их позже.
Итак, теперь мы можем сгенерировать токен обновления:
- Перейдите к следующему URI (обратите внимание, что URI перенаправления должен точно соответствовать значению, введенному в идентификаторе клиента, включая любые завершающие обратные косые черты):
- Выберите Разрешить доступ при появлении запроса.
- Браузер будет перенаправлен на ваш URI перенаправления с параметром кода, который будет выглядеть примерно так: 4/eWdxD7b-YSQ5CNNb-c2iI83KQx19.wp6198ti5Zc7dJ3UXOl0T3aRLxQmbwI. Скопируйте это значение.
Создайте основной класс с помощью:
public static String getRefreshToken(String code)
{
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost("https://accounts.google.com/o/oauth2/token");
try
{
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(5);
nameValuePairs.add(new BasicNameValuePair("grant_type", "authorization_code"));
nameValuePairs.add(new BasicNameValuePair("client_id", GOOGLE_CLIENT_ID));
nameValuePairs.add(new BasicNameValuePair("client_secret", GOOGLE_CLIENT_SECRET));
nameValuePairs.add(new BasicNameValuePair("code", code));
nameValuePairs.add(new BasicNameValuePair("redirect_uri", GOOGLE_REDIRECT_URI));
post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
org.apache.http.HttpResponse response = client.execute(post);
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer buffer = new StringBuffer();
for (String line = reader.readLine(); line != null; line = reader.readLine())
{
buffer.append(line);
}
JSONObject json = new JSONObject(buffer.toString());
String refreshToken = json.getString("refresh_token");
return refreshToken;
}
catch (Exception e) { e.printStackTrace(); }
return null;
}
GOOGLE_CLIENT_ID
, GOOGLE_CLIENT_SECRET
а также GOOGLE_REDIRECT_URI
являются предыдущими значениями.
Наконец, у нас есть токен обновления! Это значение не имеет срока действия, поэтому мы можем хранить его на каком-либо сайте, например, в файле свойств.
Доступ к Google Play Android Developer API
Получение токена доступа. Нам понадобится наш ранее обновленный токен:
private static String getAccessToken(String refreshToken){ HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost("https://accounts.google.com/o/oauth2/token"); try { List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(4); nameValuePairs.add(new BasicNameValuePair("grant_type", "refresh_token")); nameValuePairs.add(new BasicNameValuePair("client_id", GOOGLE_CLIENT_ID)); nameValuePairs.add(new BasicNameValuePair("client_secret", GOOGLE_CLIENT_SECRET)); nameValuePairs.add(new BasicNameValuePair("refresh_token", refreshToken)); post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); org.apache.http.HttpResponse response = client.execute(post); BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); StringBuffer buffer = new StringBuffer(); for (String line = reader.readLine(); line != null; line = reader.readLine()) { buffer.append(line); } JSONObject json = new JSONObject(buffer.toString()); String accessToken = json.getString("access_token"); return accessToken; } catch (IOException e) { e.printStackTrace(); } return null;
}
Теперь мы можем получить доступ к Android API. Мне интересно время истечения подписки, так что:
private static HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); private static JsonFactory JSON_FACTORY = new com.google.api.client.json.jackson2.JacksonFactory(); private static Long getSubscriptionExpire(String accessToken, String refreshToken, String subscriptionId, String purchaseToken){ try{ TokenResponse tokenResponse = new TokenResponse(); tokenResponse.setAccessToken(accessToken); tokenResponse.setRefreshToken(refreshToken); tokenResponse.setExpiresInSeconds(3600L); tokenResponse.setScope("https://www.googleapis.com/auth/androidpublisher"); tokenResponse.setTokenType("Bearer"); HttpRequestInitializer credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT) .setJsonFactory(JSON_FACTORY) .setClientSecrets(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET) .build() .setFromTokenResponse(tokenResponse); Androidpublisher publisher = new Androidpublisher.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential). setApplicationName(GOOGLE_PRODUCT_NAME). build(); Androidpublisher.Purchases purchases = publisher.purchases(); Get get = purchases.get(GOOGLE_PACKAGE_NAME, subscriptionId, purchaseToken); SubscriptionPurchase subscripcion = get.execute(); return subscripcion.getValidUntilTimestampMsec(); } catch (IOException e) { e.printStackTrace(); } return null;
}
И это все!
Некоторые шаги приведены на https://developers.google.com/android-publisher/authorization.
Ты можешь использовать com.google.api-client
а также google-api-services-androidpublisher
библиотеки.
Сначала зайдите в проект на консоли разработчика Google ( https://console.developers.google.com/).
- API & Auth -> API
- Включить "Google Play Android Developer API"
- Перейти к учетным данным -> Создать новый идентификатор клиента
- Выберите сервисный аккаунт
- Создать идентификатор клиента
- Сохраните файл p12 в безопасном месте
Затем добавьте только что созданный адрес электронной почты для учетной записи службы в консоль разработчика Google Play ( https://play.google.com/apps/publish/).
- Настройки -> Учетные записи и права -> Пригласить нового пользователя
- Вставьте
@developer.gserviceaccount.com
адрес электронной почты - Выберите "Просмотр финансовых отчетов"
- Выслать пригласительное
Теперь к коду. Добавьте следующие зависимости в ваш файл pom.xml:
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.18.0-rc</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>1.18.0-rc</version>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-androidpublisher</artifactId>
<version>v1.1-rev25-1.18.0-rc</version>
</dependency>
Затем сначала проверьте подпись:
byte[] decoded = BASE64DecoderStream.decode(KEY.getBytes());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decoded));
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (sig.verify(BASE64DecoderStream.decode(signature.getBytes())))
{
// Valid
}
Если подпись подтверждает получение сведений о подписке:
// fetch signature details from google
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(ACCOUNT_ID)
.setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/androidpublisher"))
.setServiceAccountPrivateKeyFromP12File(new File("key.p12"))
.build();
AndroidPublisher pub = new AndroidPublisher.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(APPLICATION_NAME)
.build();
AndroidPublisher.Purchases.Get get = pub.purchases().get(
APPLICATION_NAME,
PRODUCT_ID,
token);
SubscriptionPurchase subscription = get.execute();
System.out.println(subscription.toPrettyString());
Это позаботится обо всех проблемах с токеном, генерируя токен JWT, поэтому вам не придется обрабатывать его самостоятельно.
Для тех, кто хочет проверить статус подписки в Google AppEngine с Java, вот мой рабочий пример, основанный на многих кодах, найденных в SO. Я потратил пару дней, чтобы решить многие ошибки, вызванные недостатком опыта. Я вижу много предложений по проверке статуса подписки на сервере, но мне было нелегко сделать это в AppEngine. Без ответов, найденных на SO, я не смог бы придумать это.
Шаг 1
Сначала нам нужно пройти через раздел "Предварительные условия", найденный в ответе Джонатана Нагуина, пока вы не получите код из веб-браузера. Теперь у вас есть;
- ID клиента
- Секрет клиента
- URI перенаправления
- код
готовы.
Обратите внимание, что мы запускаем все коды, показанные ниже, в AppEngine. И я использовал регистратор, как это.
static final Logger log = Logger.getLogger(MyClassName.class.getName());
Шаг 2
Нам нужно получить токен обновления. Запустите код, показанный ниже, после замены [ВАШЕГО КЛИЕНТА], [ВАШЕГО КЛИЕНТА], [ВАШЕГО КОДА], [ВАШ НАПРАВЛЕННЫЙ URI] на вашу строку.
private String getRefreshToken()
{
try
{
Map<String,Object> params = new LinkedHashMap<>();
params.put("grant_type","authorization_code");
params.put("client_id",[YOUR CLIENT ID]);
params.put("client_secret",[YOUR CLIENT SECRET]);
params.put("code",[YOUR CODE]);
params.put("redirect_uri",[YOUR REDIRECT URI]);
StringBuilder postData = new StringBuilder();
for(Map.Entry<String,Object> param : params.entrySet())
{
if(postData.length() != 0)
{
postData.append('&');
}
postData.append(URLEncoder.encode(param.getKey(),"UTF-8"));
postData.append('=');
postData.append(URLEncoder.encode(String.valueOf(param.getValue()),"UTF-8"));
}
byte[] postDataBytes = postData.toString().getBytes("UTF-8");
URL url = new URL("https://accounts.google.com/o/oauth2/token");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.getOutputStream().write(postDataBytes);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer buffer = new StringBuffer();
for (String line = reader.readLine(); line != null; line = reader.readLine())
{
buffer.append(line);
}
JSONObject json = new JSONObject(buffer.toString());
String refreshToken = json.getString("refresh_token");
return refreshToken;
}
catch (Exception ex)
{
log.severe("oops! " + ex.getMessage());
}
return null;
}
Поскольку токен обновления не истекает, мы можем сохранить его где-нибудь или просто ввести жесткий код в наш код. (Нам нужно только запустить вышеуказанный код один раз, чтобы получить токен обновления.)
Шаг 3
Нам нужно получить токен доступа. Запустите код, показанный ниже, после замены [ВАШЕГО КЛИЕНТА], [ВАШЕГО СЕКРЕТА КЛИЕНТА], [ВАШЕГО ОБНОВЛЕННОГО ЖЕТОНА] вашей строкой.
private String getAccessToken()
{
try
{
Map<String,Object> params = new LinkedHashMap<>();
params.put("grant_type","refresh_token");
params.put("client_id",[YOUR CLIENT ID]);
params.put("client_secret",[YOUR CLIENT SECRET]);
params.put("refresh_token",[YOUR REFRESH TOKEN]);
StringBuilder postData = new StringBuilder();
for(Map.Entry<String,Object> param : params.entrySet())
{
if(postData.length() != 0)
{
postData.append('&');
}
postData.append(URLEncoder.encode(param.getKey(),"UTF-8"));
postData.append('=');
postData.append(URLEncoder.encode(String.valueOf(param.getValue()),"UTF-8"));
}
byte[] postDataBytes = postData.toString().getBytes("UTF-8");
URL url = new URL("https://accounts.google.com/o/oauth2/token");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.getOutputStream().write(postDataBytes);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer buffer = new StringBuffer();
for (String line = reader.readLine(); line != null; line = reader.readLine())
{
buffer.append(line);
}
JSONObject json = new JSONObject(buffer.toString());
String accessToken = json.getString("access_token");
return accessToken;
}
catch (Exception ex)
{
log.severe("oops! " + ex.getMessage());
}
return null;
}
Шаг 4
То, что я хотел знать, это просто истечь UTC подписки. Код, показанный ниже, возвращает время истечения UTC, 0 при обнаружении ошибки. Вам необходимо указать имя пакета, идентификатор продукта (= идентификатор подписки), токен доступа, полученный на шаге 3, и токен покупки, найденный в ваших данных покупки.
private long getExpireDate(String packageName,String productId,String accessToken,String purchaseToken)
{
try
{
String charset = "UTF-8";
String query = String.format("access_token=%s",URLEncoder.encode(accessToken,charset));
String path = String.format("https://www.googleapis.com/androidpublisher/v1/applications/%s/subscriptions/%s/purchases/%s",packageName,productId,purchaseToken);
URL url = new URL(path + "?" + query);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestProperty("Accept-Charset",charset);
connection.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuffer buffer = new StringBuffer();
for(String line = reader.readLine(); line != null; line = reader.readLine())
{
buffer.append(line);
}
JSONObject json = new JSONObject(buffer.toString());
return json.optLong("validUntilTimestampMsec");
}
catch (Exception ex)
{
log.severe("oops! " + ex.getMessage());
}
return 0;
}
Примечание. Идентификатор продукта или подписки - это строка, найденная на консоли разработчика. Ваш элемент подписки отображается с именем / идентификатором столбца. Похоже на это.
Description of item(product id)
Последний шаг (веселая часть)
Теперь у нас есть все компоненты, чтобы проверить, действительна подписка или нет. Мне так понравилось. Вам необходимо заменить [ИМЯ ПАКЕТА], [ИД ВАШЕГО ИЗДЕЛИЯ] на ваш.
Вам необходимо предоставить данные о покупке, которые вы можете получить с помощью Purchase#getOriginalJson(), найденной в коде iabHelper.
private boolean checkValidSubscription(String purchaseData)
{
String purchaseToken;
JSONObject json;
try
{
json = new JSONObject(purchaseData);
}
catch (JSONException e)
{
log.severe("purchaseData is corrupted");
return true; // false positive
}
purchaseToken = json.optString("purchaseToken");
if(purchaseToken.length() == 0)
{
log.severe("no purchase token found");
return true; // false positive
}
String accessToken = getAccessToken();
if(accessToken == null)
{
return true; // false positive
}
long expireDate = getExpireDate([YOUR PACKAGE NAME],[YOUR PRODUCT ID],accessToken,purchaseToken);
if(expireDate == 0)
{
log.severe("no expire date found");
return true; // false positive
}
expireDate += 86400000l; // add one day to avoid mis judge
if(expireDate < System.currentTimeMillis())
{
log.severe("subscription is expired");
return false;
}
// just for log output
long leftDays = (expireDate - System.currentTimeMillis()) / 86400000l;
log.info(leftDays + " days left");
return true;
}
Примечание для отладки
Google возвращает строку JSON для ответа. Если код не работает должным образом, запись строки JSON может помочь понять, что не так.
Я надеюсь, что это помогает кому-то.
В дополнение к замечательному ответу Джонатана Нагуина, вот версия nodejs получения токена обновления и доступа:
//This script is to retreive a refresh token and an access token from Google API.
//NOTE: The refresh token will only appear the first time your client credentials are used.
// I had to delete my client id within api console and create a new one to get the refresh token again.
//This is the downloaded json object from Google API Console. Just copy and paste over the template below.
var googleJson = {"web":{"auth_uri":"","client_secret":"","token_uri":"","client_email":"","redirect_uris":[""],"client_x509_cert_url":"","client_id":"","auth_provider_x509_cert_url":"","javascript_origins":[""]}};
//Retrieved from OAuth
var code = ''; // Retrieved from the response of the URL generated by printGoogleAuthUrl(). You will need to be logged in as your publisher. Copy and paste the generated url. Copy the code parameter into this variable.
var refreshToken = ''; // Retrieved from the printRefreshToken() function call. Requires the code variable to be filled out.
var accessToken = ''; // Retrieved from the printAccessToken() function call. Requires the refreshToken variable to be filled out.
var querystring = require('querystring');
var https = require('https');
var fs = require('fs');
function printGoogleAuthUrl()
{
console.log("https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=" + googleJson.web.redirect_uris[0] + "&client_id=" + googleJson.web.client_id);
}
function printRefreshToken()
{
var post_data = querystring.stringify({
'grant_type' : 'authorization_code',
'client_id' : googleJson.web.client_id,
'client_secret' : googleJson.web.client_secret,
'code' : code,
'redirect_uri' : googleJson.web.redirect_uris[0]
});
var post_options = {
host: 'accounts.google.com',
port: '443',
path: '/o/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length
}
};
var post_req = https.request(post_options, function(res) {
res.setEncoding('utf8');
var data = "";
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function(){
var obj = JSON.parse(data);
if(obj.refresh_token)
{
refreshToken = obj.refresh_token;
}
else
{
console.log("No refresh token found. I had to clear the web client id in Google Api Console and create a new one. There might be a better way here.");
}
console.log(data);
});
});
post_req.write(post_data);
post_req.end();
}
function printAccessToken()
{
var post_data = querystring.stringify({
'grant_type' : 'refresh_token',
'client_id' : googleJson.web.client_id,
'client_secret' : googleJson.web.client_secret,
'refresh_token' : refreshToken
});
var post_options = {
host: 'accounts.google.com',
port: '443',
path: '/o/oauth2/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length
}
};
var post_req = https.request(post_options, function(res) {
res.setEncoding('utf8');
var data = "";
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function(){
var obj = JSON.parse(data);
if(obj.access_token)
accessToken = obj.access_token;
else
console.log("No access token found.");
console.log(data);
});
});
post_req.write(post_data);
post_req.end();
}
printGoogleAuthUrl();
//printRefreshToken();
//printAccessToken();
Для тех, кто ищет более свежий ответ с AndroidPublisher v3, пожалуйста, посмотрите здесь: /questions/50429467/primeryi-dlya-androidpublisher-v3-klientskoj-biblioteki-google-api-dlya-java/55060418#55060418.
Нет необходимости иметь дело с refreshToken и accessToken, а всего несколько строк кода.
Для клиента Android я нашел этот «официальный» образец кода: https://github.com/googlesamples/android-play-publisher-api/blob/master/v3/java/src/com/google/play/developerapi/samples/AndroidPublisherHelper.java, и есть варианты получить AndroidPublisher как с файлом .p12, так и с учетными данными приложения.
Я уверен, что вы должны использовать свой идентификатор клиента, а не адрес электронной почты. Это выглядит так: 37382847321922.apps.googleusercontent.com
См. https://developers.google.com/android-publisher/authorization
client_id=<the client ID token created in the APIs Console>
И я уверен, что вам не нужен файл P12. Вам нужно только
client_secret=<the client secret corresponding to the client ID>
Попробуйте сначала сделать это вручную из командной строки с помощью wget.