Как лучше сохранить статус покупки InApp локально?
Я нахожусь на грани завершения моего первого приложения, и еще одна оставшаяся вещь - реализовать биллинг IAP, поэтому я сейчас читаю довольно много о теме (включая проблемы безопасности, такие как шифрование, обфускация и прочее).
Мое приложение - это бесплатная версия с возможностью обновления до полной версии через IAP, так что будет только один элемент покупной покупки "премиум". У меня есть несколько вопросов по этому поводу:
В примере API API IAP (trivialdrivesample) всегда есть проверка IAP в MainActivity, чтобы узнать, покупает ли пользователь премиум-версию, выполненную с помощью
mHelper.queryInventoryAsync(mGotInventoryListener);
Моя первая забота:
Это означает, что пользователь всегда должен иметь подключение к Интернету/данным при запуске приложения, чтобы иметь возможность перейти на премиум-версию вправо? Что делать, если у пользователя нет подключения к Интернету? Думаю, он подойдет с облегченной версией, что мне будет неприятно.
Итак, я подумал о том, как сохранить статус isPremium локально, либо в SharedPrefs, либо в базе данных приложений. Теперь я знаю, что вы не можете остановить хакера, чтобы перепроектировать приложение, несмотря ни на что, даже если я не владею сервером для проверки на стороне сервера.
Тем не менее, просто невозможно сохранить флаг "isPremium" где-то, так как это было бы слишком легко заметить.
Итак, я думал о чем-то вроде этого:
- Пользователь покупает премиум
- Приложение получает идентификатор IMEI/Device-ID, а XOR кодирует его с помощью жестко закодированного ключа String, сохраняет его локально в базе данных приложений.
Теперь, когда пользователь снова запустит приложение:
- Приложение получает закодированную строку из базы данных, декодирует ее и проверяет, расшифрован ли файлString == IMEI. Если да → премиум
- Если нет, тогда будет вызван обычный запросInventoryAsync, чтобы узнать, покупает ли пользователь премию.
Что вы думаете об этом подходе? Я знаю, что это не supersecure, но для меня важнее, чтобы пользователь не был раздражен (например, с обязательным подключением к Интернету), чем приложение будет недоступно (что в любом случае невозможно).
У вас есть другие советы?
Еще одна вещь, о которой я в настоящее время не знаю, заключается в том, как восстановить статус транзакции, когда пользователь удаляет/переустанавливает приложение. Я знаю, что у API есть некоторый механизм для этого, и, кроме того, моя база данных может быть экспортирована и импортирована через приложение (так что закодированный флаг isPremium будет также экспортироваться/импортироваться). Хорошо, я думаю, это был бы другой вопрос, когда настало время, -)
Любые мысли и комментарии к этому подходу приветствуются, считаете ли вы, что это хорошее решение? Или я пропущу что-то/заголовок в неправильном направлении?
Ответы
Ответ 1
Я тоже делал те же исследования, но во время тестирования я понял, что вам не нужно его хранить, так как Google делает все необходимое для кеширования, и я подозреваю (хотя я еще не исследовал его), что они делают так как надежно (насколько это возможно в их интересах!)
Итак, вот что я делаю
// Done in onCreate
mHelper = new IabHelper(this, getPublicKey());
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log("Problem setting up In-app Billing: " + result);
} else {
Log("onIabSetupFinished " + result.getResponse());
mHelper.queryInventoryAsync(mGotInventoryListener);
}
}
});
// Called by button press
private void buyProUpgrade() {
mHelper.launchPurchaseFlow(this, "android.test.purchased", 10001,
mPurchaseFinishedListener, ((TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId());
}
// Get purchase response
private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase)
{
if (result.isFailure()) {
Log("Error purchasing: " + result);
return;
}
else if (purchase.getSku().equals("android.test.purchased")) {
Log("onIabPurchaseFinished GOT A RESPONSE.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
}
};
// Get already purchased response
private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// handle error here
Log("Error checking inventory: " + result);
}
else {
// does the user have the premium upgrade?
mIsPremium = inventory.hasPurchase("android.test.purchased");
setTheme();
Log("onQueryInventoryFinished GOT A RESPONSE (" + mIsPremium + ").");
}
}
};
Итак, что здесь происходит?
IAB настроен и вызывает startSetup
, при успешном завершении (если он был запущен один раз с подключением к Интернету и настроен правильно, он всегда будет успешным), мы вызываем queryInventoryAsync
, чтобы узнать, что (опять же, если это было вызвано во время онлайн, он всегда работает в автономном режиме).
Итак, если покупка завершена успешно (как это может быть сделано только в режиме онлайн), мы вызываем queryInventoryAsync
, чтобы убедиться, что она была вызвана в режиме онлайн.
Теперь нет необходимости хранить что-либо, связанное с покупками, и делает ваше приложение намного менее взломанным.
Я тестировал это много способов, режим полета, снова отключая устройства, и единственное, что его испортило, - это очистка данных в некоторых приложениях Google на телефоне (вряд ли произойдет!).
Пожалуйста, внесите свой вклад в это, если у вас есть другой опыт, мое приложение все еще находится на ранней стадии тестирования.
Ответ 2
Я рефакторизую ne0 ответ на статический метод, включая комментарии от snark.
Я вызываю этот метод при запуске приложения - вам нужно включить свои функции в TODO
/**
* This is how you check with Google if the user previously purchased a non-consumable IAP
* @param context App Context
*/
public static void queryPlayStoreForPurchases(Context context)
{
final IabHelper helper = new IabHelper(context, getPublicKey());
helper.startSetup(new IabHelper.OnIabSetupFinishedListener()
{
public void onIabSetupFinished(IabResult result)
{
if (!result.isSuccess())
{
Log.d("InApp", "In-app Billing setup failed: " + result);
}
else
{
helper.queryInventoryAsync(false, new IabHelper.QueryInventoryFinishedListener()
{
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
// If the user has IAP'd the Pro version, let 'em have it.
if (inventory.hasPurchase(PRO_VERSION_SKU))
{
//TODO: ENABLE YOUR PRO FEATURES!!
Log.d("IAP Check", "IAP Feature enabled!");
}
else
{
Log.d("IAP Check", "User has not purchased Pro version, not enabling features.");
}
}
});
}
}
});
}
Это будет работать во всех перезагрузках и без сетевого подключения, если пользователь приобретет элемент.
Ответ 3
Поскольку вы уже знаете, что невозможно сделать его недоступным с помощью этой системы, я бы рекомендовал не пытаться предотвратить взлом. То, что вы предлагаете, называется "Безопасность через безвестность" и обычно плохой идеей.
Мой совет: сначала попробовать queryInventoryAsync()
и только проверить свой флаг isPremium, если нет подключения к Интернету.
Есть также несколько потенциальных других способов обойти это, например, иметь отдельные бесплатные и премиальные приложения вместо покупки приложения. Как другие люди справляются с этим, и инструменты, которые Google предоставляет, могут потребовать расследования.
queryInventoryAsync
будет автоматически принимать во внимание удаление и переустановку, поскольку он отслеживает покупки для зарегистрированного пользователя.