android - studio - ¿Cómo se puede guardar mejor el estado de compra de InApp localmente?
in app billing android studio (3)
Como ya sabes que es imposible hacer que no sea imposible usar este sistema, recomendaría no intentar evitar el pirateo. Lo que está proponiendo se conoce como "Seguridad a través de la oscuridad" y generalmente es una mala idea.
Mi consejo sería probar queryInventoryAsync()
primero, y solo verificar su indicador ''isPremium'' si no hay conexión a Internet.
También hay algunas otras posibles formas de solucionar esto, como tener aplicaciones gratuitas y premium separadas, en lugar de una compra en la aplicación. Cómo otras personas manejan esto y las herramientas que Google pone a disposición podrían justificar una investigación.
queryInventoryAsync
tendrá en cuenta automáticamente la desinstalación y las reinstalaciones, ya que realiza un seguimiento de las compras para el usuario que ha iniciado sesión.
Estoy a punto de terminar mi primera aplicación, y lo último que queda es implementar la facturación de IAP, por eso actualmente estoy leyendo mucho sobre el tema (incluidas cuestiones de seguridad como el cifrado, la ofuscación y otras cosas).
Mi aplicación es una versión gratuita, con la capacidad de actualizar a versión completa a través de IAP, por lo que solo habría un artículo de compra administrado "premium". Tengo algunas preguntas sobre esto:
En el ejemplo de la API de IAP de Google (trivialdrivesample), siempre está la comprobación IAP en MainActivity para ver si el usuario compró la versión premium, realizada a través de
mHelper.queryInventoryAsync (mGotInventoryListener);
Mi primera preocupación: esto significa que el usuario siempre necesita tener una conexión de internet / datos al inicio de la aplicación, para poder cambiar a la versión premium ¿no? ¿Qué pasa si el usuario no tiene una conexión a Internet? Optaría por la versión lite, supongo, que me resultaría molesto.
Así que pensé en cómo guardar el estado de isPremium localmente, ya sea en SharedPrefs o en la base de datos de la aplicación. Ahora, sé que no se puede detener a un hacker para que realice una ingeniería inversa de la aplicación, sin importar qué, incluso así porque no tengo un servidor para realizar una validación del lado del servidor.
Sin embargo, uno simplemente no puede guardar una bandera "isPremium" en alguna parte, ya que sería demasiado fácil de detectar.
Así que estaba pensando en algo como esto:
- El usuario compra Premium
- La aplicación obtiene el IMEI / Device-ID y XOR lo codifica con una clave de cadena codificada, lo guarda localmente en la base de datos de la aplicación.
Ahora cuando el usuario inicia la aplicación nuevamente:
- La aplicación obtiene la Cadena codificada de la base de datos, la descodifica y comprueba si decodedString == IMEI. Si es así -> premium
- Si no, se llamará al queryInventoryAsync normal para ver si el usuario compró premium.
¿Qué piensas de ese enfoque? Sé que no es supersecreto, pero para mí es más importante que el usuario no se moleste (como con la conexión a Internet obligatoria), que la aplicación será imposible de descifrar (lo que de todos modos es imposible). ¿Tienes algunos otros consejos?
Otra cosa, de la que actualmente no tengo ni idea, es cómo restaurar el estado de la transacción cuando el usuario desinstala / reinstala la aplicación. Sé que la API tiene algún mecanismo para eso, y adicionalmente mi base de datos puede exportarse e importarse a través de la aplicación (por lo que la bandera isPremium codificada sería exportable / importable también). Ok, creo que esa sería otra pregunta, cuando sea el momento adecuado ;-)
Cualquier comentario o comentario sobre este enfoque es bienvenido, ¿cree que es una buena solución? ¿O me estoy perdiendo algo / yendo en una dirección equivocada?
Refactoré la respuesta de ne0 en un método estático, incluidos los comentarios de snark.
Llamo a este método cuando se inicia mi aplicación: tendrá que habilitar sus funciones en 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.");
}
}
});
}
}
});
}
Esto funcionará en reinicios y sin una conexión de red, siempre que el usuario haya comprado el artículo.
Yo también estaba haciendo las mismas investigaciones, pero durante las pruebas descubrí que no es necesario que lo almacene, ya que Google hace todo el almacenamiento en caché que necesita y sospecho (aunque no lo he investigado) que lo hacen de forma segura. como sea posible (¡ya que también les interesa!)
Entonces, esto es lo que hago
// 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 + ").");
}
}
};
Entonces, ¿qué pasa aquí?
El IAB está configurado y llama a startSetup
, una vez que se haya completado satisfactoriamente (siempre que se haya ejecutado una vez con una conexión a Internet y esté configurado correctamente, siempre tendrá éxito) llamamos a queryInventoryAsync
para averiguar qué se ha comprado (de nuevo si esto ha sido llamado mientras está en línea, siempre funciona sin conexión).
Entonces, si una compra se completa con éxito (como solo se puede hacer mientras está en línea), llamamos a queryInventoryAsync
para asegurarnos de que se haya invocado mientras estuvo en línea.
Ahora no hay necesidad de almacenar nada que ver con las compras y hace que su aplicación sea mucho menos pirateable.
Lo he probado de muchas maneras, modo avión, apagando los dispositivos una y otra vez y lo único que lo estropea es borrar datos en algunas de las aplicaciones de Google en el teléfono (¡No es probable que suceda!).
Por favor, contribuya a esto si tiene experiencias diferentes, mi aplicación todavía está en etapa de prueba temprana.