React-native-iap: IOS comprueba si la suscripción de renovación automática todavía está activa

Creado en 27 sept. 2018  ·  61Comentarios  ·  Fuente: dooboolab/react-native-iap

Versión de react-native-iap

2.2.2

Plataformas a las que te enfrentaste con el error (¿IOS o Android o ambos?)

IOS

Comportamiento esperado

Usando la api de RNIAP, podríamos verificar si una suscripción de renovación automática aún está activa

Comportamiento real

En Android, estoy haciendo esto para verificar que:

export const isUserSubscriptionActive = async (subscriptionId) =>{
    // Get all the items that the user has
    const availablePurchases = await getAvailablePurchases();
    if(availablePurchases !== null && availablePurchases.length > 0){
        const subscription = availablePurchases.find((element)=>{
            return subscriptionId === element.productId;
        });
        if(subscription){
             // check for the autoRenewingAndroid flag. If it is false the sub period is over
              return subscription["autoRenewingAndroid"] == true;
            }
        }else{
            return false;
        }
    }
}

En ios no hay una bandera para verificar eso, y el método getAvailablePurchases devuelve todas las compras realizadas, incluso las suscripciones que no están activas en este momento.

¿Hay alguna forma de comprobar esto?

Saludos,
Marcos

📱 iOS 🚶🏻 stale

Comentario más útil

Esta es la función que estoy usando que funciona tanto en Android como en iOS, clasificando correctamente en iOS para asegurarnos de que obtengamos los últimos datos de recibo:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

Todos 61 comentarios

También estoy trabajando para resolver esto. No lo he probado todavía, pero por lo que estoy leyendo, estoy deduciendo que es posible crear un "secreto compartido" en iTunes Connect y pasarlo para validarReceiptIos con la clave "contraseña". Creo que esto devolverá un objeto JSON que contendrá un código que indica el estado de validación de la suscripción y las claves latest_receipt, latest_receipt_info y latest_expired_receipt info, entre otras, que puede utilizar para determinar el estado de la suscripción. Literalmente estoy descubriendo esto, así que todavía tengo que probarlo. Es lo que estoy reuniendo a partir de los problemas y los documentos de Apple. Si esto funciona, realmente debería quedar muy claro en la documentación en lugar de estar enterrado en los problemas.
Creo que los siguientes enlaces son relevantes:
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

203 # 237

EDITAR : Puedo confirmar que el proceso que mencioné anteriormente funciona. Creo que deberíamos trabajar para obtener una explicación completa de esto en los documentos. Implementaría algo como esto en el lanzamiento de la aplicación para determinar si una suscripción ha expirado:

RNIap.getPurchaseHistory()
        .then(purchases => {
                RNIap.validateReceiptIos({
                   //Get receipt for the latest purchase
                    'receipt-data': purchases[purchases.length - 1].transactionReceipt,
                    'password': 'whateveryourpasswordis'
                }, __DEV__)
                    .then(receipt => {
                       //latest_receipt_info returns an array of objects each representing a renewal of the most 
                       //recently purchased item. Kinda confusing terminology
                        const renewalHistory = receipt.latest_receipt_info
                       //This returns the expiration date of the latest renewal of the latest purchase
                        const expiration = renewalHistory[renewalHistory.length - 1].expires_date_ms
                       //Boolean for whether it has expired. Can use in your app to enable/disable subscription
                        console.log(expiration > Date.now())
                    })
                    .catch(error => console.log(`Error`))
        })

@kevinEsherick

¡Brillante!

Solo algunas cosas que no son tan triviales sobre su código;

  1. Debería ser

const expiration =

en lugar de (probablemente solo un error tipográfico)

const expiration =

2) Para aquellos que no saben qué es 'contraseña': 'whateveryourpasswordis':

Puede crear su clave secreta compartida para sus paquetes en la aplicación y usarla en su aplicación

Los pasos están aquí: https://www.appypie.com/faqs/how-can-i-get-shared-secret-key-for-in-app-purchase

+1 para actualizar los documentos, también, tal vez @dooboolab pueda separar los casos de uso en IOS y Android parece que hay algunas cosas diferentes. Te puedo ayudar si quieres.

Saludos

@kevinEsherick

¿Qué sucede con las suscripciones de renovación automática?

Parece que expires_date_ms es la primera fecha de vencimiento, no se actualiza

He probado esto:

  1. Suscribirse a un artículo de suscripción
  2. Verifique la fecha de vencimiento (como 5 minutos después)
  3. Espera 10 minutos
  4. La suscripción aún activa parece ser una suscripción de renovación automática
  5. Si verifico que el vencimiento del último recibo de renovación sigue siendo el mismo que en el punto 2

¿Algunas ideas?

Mi mal, la fecha de vencimiento parece estar actualizada, solo lleva un tiempo.

Dejaré este comentario aquí en caso de que alguien se encuentre en el mismo escenario.

Saludos

  1. Debería ser

const expiration =

en lugar de (probablemente solo un error tipográfico)

const expiration =

Ups, sí, tienes razón, fue un error tipográfico. Había cambiado el nombre para que su significado fuera más obvio, pero olvidé cambiar todas las apariciones. Lo he editado, gracias.

Y no he tenido ese escenario exacto con fecha de vencimiento, pero hubo algunas cosas que no se renovaron automáticamente en absoluto, aunque de alguna manera se resolvió por sí solo (lo que me preocupa, pero no puedo reproducir, así que no sé qué más hacer). El problema que tuvo es bastante común. Es el comportamiento esperado por parte de Apple, por lo que debe dejar un período de amortiguación para que la suscripción se renueve antes de cancelar la suscripción del usuario. Este problema en SO se explica con más detalle: https://stackoverflow.com/questions/42158460/autorenewable-subscription-iap-renewing-after-expiry-date-in-sandbox

¡Gracias! Excelente información, podría ser la razón

Acerca de este problema, creo que el código @kevinEsherick debería estar en los documentos 👍 ¡Es una excelente manera de verificar los subs!

Si. Agradecería un buen documento en readme si alguno de ustedes solicita un PR . Gracias.

Hola tios. Agregué la sección de preguntas y respuestas en el archivo Léame. Espero que alguno de ustedes pueda darnos un PR por este problema.

Buscaré hacer un PR en los próximos días :)

También estoy buscando usar esta biblioteca específicamente para suscripciones. Tanto iOS como Android. Se agradecería mucho documentar la forma adecuada de determinar si un usuario tiene una suscripción válida. 😄

@curiousdustin La implementación de Apple en esto es realmente terrible si se refiere al medio . Dado que necesita manejar este tipo de cosas en su backend, no pudimos admitirlo completamente en nuestro módulo. Sin embargo, puede obtener availablePurchases en Android usando nuestro método getAvailablePurchases que no funcionará en el producto de suscripción de iOS.

Entonces, ¿estás diciendo que la solución en la que están trabajando @ marcosmartinez7 y @kevinEsherick en este hilo NO es una solución, y que es necesario un servidor backend que no sea el de Apple para confirmar el estado de una suscripción de iOS?

Al leer el artículo de Medium que publicó junto con los documentos de Apple, parece que se prefiere usar un servidor, pero no es imposible determinar el estado de la suscripción solo con el dispositivo.

Lamento haber perdido alguna información en mi escritura. Me encargaré de esto ahora.
Apple sugiere guardar el receipt en su propio servidor para evitar un ataque intermedio. Por lo tanto, puede verificar más tarde ese recibo para saber si el recibo es válido o si la suscripción aún está disponible.

Sin embargo, no es imposible probar con react-native-iap ya que proporcionamos la recuperación directa del recibo de validación al servidor de Apple (sandbox y producción) que afortunadamente @ marcosmartinez7 y @kevinEsherick funcionaron.

Creo que actualmente esta es la solución para comprobar la suscripción de ios .

Hola chicos, lo siento, han pasado más de unos pocos días, he estado trabajando, pero aún enviaré ese RP para actualizar el archivo README. @curiousdustin, el método sobre el que escribí anteriormente definitivamente funciona y lo

Gracias por la actualización.

Una cosa más que noté en tu ejemplo.

'recibo-datos': compras [compras.longitud - 1] .transactionReceipt,
...
const expiration = renewalHistory [renewalHistory.length - 1] .expires_date_ms

En mis pruebas, las compras y el historial de renovación no están necesariamente en ningún orden en particular. ¿No necesitaríamos ordenarlos o algo para verificar que estamos usando el que pretendemos?

Aquí igual. Las compras y el historial de renovación definitivamente no están ordenadas, por lo que primero tenemos que ordenar. En desarrollo, eso termina siendo muchas iteraciones.

Esta es la función que estoy usando que funciona tanto en Android como en iOS, clasificando correctamente en iOS para asegurarnos de que obtengamos los últimos datos de recibo:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

@andrewze

En el escenario de Android, si la suscripción es renovable automáticamente, se devolverá en compras disponibles

@curiousdustin @andrewzey Tienes razón, las compras no están ordenadas. newalHistory / latest_receipt_info, sin embargo, siempre se solicita para mí. Recuerdo haberme dado cuenta de que las compras no estaban ordenadas, pero pensé que había encontrado alguna razón por la que esto no era realmente preocupante. Investigaré esto en un par de horas una vez que llegue a casa. No quiero enviar un PR hasta que tengamos esto resuelto.
EDITAR: Entonces, la razón por la que no creo que deba ordenar las compras es porque expires_date_ms devuelve lo mismo independientemente de la compra que haya consultado. ¿No es esto lo mismo para ustedes? ¿O hay algo de información que necesita que requiera clasificación? Déjame saber lo que piensas. Estoy de acuerdo en que los documentos deben dejar en claro que las compras no se ordenan cronológicamente, pero por lo que puedo decir, la clasificación no es necesaria para obtener la fecha de vencimiento.

@kevinEsherick ¡Gracias! En mi caso, no se ordenaron las compras ni el Historial de renovación.

@kevinEsherick @andrewzey ¿Pueden dar PR en esta solución? Nos gustaría compartir con otros que están sufriendo.

Seguro que lo prepararé justo después de que terminemos con el lanzamiento de nuestra aplicación pública =).

Estaba esperando más conversación sobre mi último comentario. Mencioné algunos problemas que siguen sin resolverse. Por favor vea arriba como referencia. Una vez que lo solucionemos, uno de nosotros puede hacer un PR.

@kevinEsherick Ah, lo siento, me perdí eso.

expires_date_ms es definitivamente único para mí en cada recibo de renovación. ¿Quizás lo he entendido mal? Me di cuenta de que la matriz de recibos de renovaciones adjunta a cada recibo decodificado es la misma independientemente del recibo seleccionado, por lo que pude ver que puede tener razón en lo siguiente (de mi ejemplo) que no se requiere:

const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );

Pero lo hice de todos modos para tratar de ser preciso y compensar las posibles discrepancias en la respuesta API real de lo que Apple documenta. No creo que una operación de clasificación sea muy costosa, dada la cantidad total de recibos que probablemente encontrará para las suscripciones de renovación automática.

¿Alguna ETA en el PR? Estoy considerando cambiarme de este paquete, ya que no maneja este caso en absoluto.

@schumannd Dado que el PR sería solo para actualizar los documentos, puede pasar a react-native-iap ahora.

Confirmé que mi fragmento de código funciona sin problemas en producción con nuestra aplicación lanzada recientemente: https://itunes.apple.com/us/app/rp-diet/id1330041267?ls=1&mt=8

EDITAR Definitivamente haré el PR (lo tengo en mi lista "Devolver a OSS" en nuestro Trello), pero después de la locura del fin de semana del viernes negro 😄

@schumannd Apoyo lo que dijo @andrewzey . Todo lo que diría un PR está contenido aquí en esta publicación. Tenía la intención de hacerlo, pero nos tomó un tiempo resolver exactamente lo que se debe hacer, y eso, combinado con viajes y agitadas horas de inicio, significa que aún no he tenido tiempo para hacer publicidad. Todavía planeo hacerlo pronto, pero cualquier otra persona puede intervenir mientras tanto si lo desea. ¡Y @andrewzey felicitaciones tío! ¡Se ve genial, lo descargaré!

No estoy convencido de que este hilo documente con precisión la forma correcta de administrar las suscripciones de renovación automática en iOS, sino que describe un flujo de trabajo de "solución alternativa" según la API expuesta por esta biblioteca. Sin embargo, esto es completamente una especulación de mi parte y soy muy consciente de que podría estar equivocado, por lo que me gustaría recibir comentarios sobre las observaciones que he descrito a continuación.

He estado investigando la forma correcta de administrar las suscripciones de renovación automática en React Native durante semanas sin éxito, así que cambié de marcha y comencé a investigar cómo administrarlas de forma nativa en Objective-C / Swift. Esto me dio un punto de referencia para comparar las API nativas de StoreKit y el uso documentado con lo que se ha implementado en esta biblioteca.

Este artículo es la mejor explicación que he encontrado hasta ahora sobre cómo la compra, validación y restauración de suscripciones deben ser manejadas por una aplicación nativa de iOS: https://www.raywenderlich.com/659-in-app-purchases-auto -renewable-subscriptions-tutorial y aunque todas las API se utilizan en react-native-iap , están implementando un flujo de trabajo diferente.

Una diferencia clave que veo es el uso de appStoreReceiptUrl .

StoreKit proporciona el appStoreReceiptUrl que, según tengo entendido, es la forma correcta de cargar la información actual de compra / recibo para una aplicación iOS. Si bien veo que se usa y se hace referencia a esto en el código react-native-iap , parece estar en un contexto diferente al del flujo de trabajo documentado en el artículo de referencia.

No estoy seguro si / cómo esta diferencia de implementación es problemática, pero la API react-native-iap parece estar creando una dependencia indiscutible en StoreKit's restoreCompletedTransactions que es lo que finalmente se llama getPurchaseHistory .

Me parece que la API react-native-iap debería admitir el siguiente flujo de trabajo para iOS:

  • Intente cargar el recibo de la aplicación actual (a través de appStoreReceiptUrl ) al iniciar la aplicación
  • Brindar la oportunidad de validar el recibo.
  • Si appStoreReceiptUrl es nulo, inicie una "restauración" en el dispositivo actual que luego establecería el valor de appStoreReceiptUrl y se puede reintentar la lógica de validación

Pensamientos

Nuevamente, podría estar malinterpretando la implementación de esta biblioteca o la implementación nativa a la que se hace referencia.

@sellmeadog

Creo que un punto clave que también se menciona en el artículo que vinculó es este:

Nota: La aplicación del proyecto ejecuta SelfieService, que acepta recibos y los carga directamente al servicio de validación de recibos de Apple. Se trata de un "truco" para mantener el tutorial centrado en configurar las suscripciones. En una aplicación real, debe usar un servidor remoto para esto, no la aplicación.

Me tomó un tiempo darme cuenta de todo el asunto de la suscripción renovable automáticamente, pero por lo que entiendo, la mejor manera de manejarlo es usar su propio backend como la única fuente de verdad:

  • envíe todos los datos de la transacción inmediatamente a su backend
  • proporcionar algún medio de reenvío en caso de problemas de transmisión (por ejemplo, restaurar compras)
  • periódicamente (por ejemplo, una vez al día) verifique todos los recibos almacenados en su backend
  • la aplicación consulta el backend para conocer el estado de la suscripción, por ejemplo, volver desde el fondo.

A mí me parecía que era la única forma sensata de gestionar las suscripciones. También he verificado este proceso con otros desarrolladores que utilizan suscripciones autorrenovables comercialmente durante algún tiempo.

Entonces, mi respuesta a la pregunta de OP sería: verifique las suscripciones periódicamente en el backend, no en el frontend

@schumannd

No estoy seguro de que estemos entendiendo o hablando del mismo tema.

La validación del recibo debe realizarse en el servidor. Según la propia documentación de Apple :

No llame al servidor de la App Store /verifyReceipt endpoint desde su aplicación.

Interpreté el "truco" en el artículo de referencia como específicamente las llamadas directas de la aplicación de muestra a /verifyReceipt directamente desde la aplicación para abreviar en lugar de delegar a un servidor backend.

Me parece que Apple / App Store es y debe ser la única fuente de verdad. Apple finalmente está procesando compras, renovaciones, cancelaciones, etc. y generando recibos en consecuencia y hace que esos datos estén disponibles a través de las API StoreKit .

react-native-iap maneja la compra y restauración de compras sin problemas, pero sigo pensando que hay una brecha en el acceso a los datos de recibos de la App Store que están disponibles localmente en appStoreReceiptUrl que es nuevamente la forma documentada de Apple para acceder al recibo. datos :

Para recuperar los datos del recibo, use el método appStoreReceiptURL de NSBundle para localizar el recibo de la aplicación ...

El flujo de trabajo que entiendo al leer los documentos de Apple y hacer referencia a las implementaciones nativas de Object-C / Swift es el siguiente:

  • Cargar productos para comprar
  • Compre un producto (consumible, no consumible, suscripción, no importa)
  • Un recibo de esa compra se almacena localmente en appStoreReceiptUrl por StoreKit detrás de escena
  • La aplicación debe enviar los datos del recibo a un servidor backend (no a la App Store /verifyReceipt directamente) para su validación según sea necesario
  • appStoreReceiptUrl se actualiza en StoreKit siempre que se produzcan cancelaciones y / o renovaciones (suponiendo que los delegados de StoreKit adecuados estén registrados correctamente)
  • Cuando appStoreReceiptUrl es nulo, porque el usuario está en un dispositivo nuevo, etc., use el flujo de trabajo de restauración de compra que recuperará los datos de recibos actuales de Apple / AppStore y StoreKit persistirá localmente en appStoreReceiptUrl detrás de escena

Nuevamente, react-native-iap maneja todo esto excepto para cargar los datos del recibo desde appStoreReceiptUrl . Creo que un método de getReceipt que solicite el recibo local aliviaría la carga de administrar las suscripciones y / o cualquier otra compra.

Apple ha hecho que esto sea mucho más difícil de razonar de lo que debería ser.

appStoreReceiptUrl se actualiza en StoreKit siempre que se produzcan cancelaciones y / o renovaciones (suponiendo que los delegados de StoreKit adecuados estén registrados correctamente)

Esto describe el problema que tengo con este enfoque: no se le notifica de ninguna renovación / cancelación / vencimiento, etc. si el usuario no abre su aplicación después con una conexión a Internet que funcione.

Aparte de eso, parece una buena alternativa al enfoque que describí. Me pregunto cuál se usa más para aplicaciones de producción.

@sellmeadog Creo que es la misma idea que publiqué aquí: # 356
Validar recibos localmente es un método, recomienda Apple. Eso es completamente independiente de cualquier solicitud de servidor / red.

Sí. Hay desventajas con respecto a "sin conexión de red, sin validación".
Pero hay casos de uso para eso.
Quiero una validación rápida del recibo en cada lanzamiento de la aplicación y no quiero hacer un seguimiento de las compras dentro de la aplicación por mi cuenta a través de UserDefaults o Keychain . StoreKit ya proporciona un mecanismo de almacenamiento.

¿Cómo se supone que debe verificar si el usuario de IOS renovó o canceló su suscripción sin pedirle que inicie sesión en la cuenta de iTunes a través de algo como getAvailablePurchases?

Por ejemplo:
Tras la primera compra de la suscripción, validamos el recibo y guardamos la fecha de vencimiento en su perfil de base de fuego. Usamos ese nodo de base de fuego para verificar en el inicio si tienen una suscripción activa. El problema surge cuando esa fecha de vencimiento ha pasado y comienza el nuevo período de facturación. ¿Cómo podemos verificar si se renovaron automáticamente o se cancelaron? Como se mencionó, estábamos usando getAvailablePurchases, pero eso solicita el inicio de sesión de iTunes como lo haría un botón de restauración, lo cual es una interfaz de usuario terrible

También me pregunto lo mismo.

En nuestra aplicación no lo necesitamos, ya que tenemos una API del lado del servidor que se comunica directamente con Apple para verificar el estado. Después de la compra original, los datos del recibo se almacenan en nuestros servidores y luego se utilizan para solicitar el estado actualizado.

Sin embargo, creo que esto debería ser posible en el lado del dispositivo. No soy un experto en esto, pero según tengo entendido, la aplicación recibirá transacciones / recibos actualizados del sistema operativo cada vez que se produzca una renovación. Sospecho que este complemento está manejando estos recibos al iniciar la aplicación. (Veo un montón de registros cuando se ejecuta desde Xcode, que parecen estar procesando múltiples transacciones) PERO, por lo que puedo decir, no hay forma de aprovechar esos eventos en el código JS.

¿Podríamos obtener más información sobre esto?

@csumrell @curiousdustin , ¿tienen este comportamiento confirmado en un entorno de producción? Me pregunto si podría estar ocurriendo simplemente debido al sandboxing, con la cuenta de sandbox necesitando iniciar sesión ya que presumiblemente hay otra cuenta que no es sandbox en su dispositivo, mientras que en producción el usuario presumiblemente iniciará sesión en su cuenta normal y esto módulo podría obtener compras sin solicitar inicio de sesión.

Lo siento, NO he confirmado que getAvailablePurchases() genere solicitudes de contraseña en producción.

Simplemente asumí que lo haría porque los documentos hablan de que este método se usa para la función comúnmente conocida como Restauración de compras, que en mi experiencia implica autenticación, al menos en iOS.

@hyochan @JJMoon ¿Podríamos obtener más información sobre esto también? Específicamente, ¿hay alguna manera de obtener compras históricas o disponibles de manera confiable sin que el sistema operativo intente la autenticación? También consulte los comentarios de la pregunta 2 anterior, con respecto a la capacidad de reaccionar a las compras que se detectan y procesan al iniciar la aplicación.

@curiousdustin sí, así que cuando salgo de mi cuenta normal de iTunes y en la cuenta de la zona de pruebas, ya no me pide que inicie sesión cuando se llama a este método. Entonces, mi conjetura / esperanza es que en producción, donde los usuarios no tienen una cuenta de sandbox en uso, el problema no debería aparecer. Las pruebas beta deberían funcionar de la misma manera que la producción porque los usuarios no tienen cuentas de sandbox configuradas en el dispositivo, es solo un problema de los dispositivos de desarrollo. Así que voy a probar esto mañana con usuarios beta y les haré saber lo que encuentro.
EDITAR: Puedo confirmar que esto sucede en dispositivos que no son sandbox. Esta es una experiencia de usuario terrible y es bastante problemática. Alguien, alguna idea?

Entonces, cuando valida en su propio servidor llamando al servidor de la App Store / verifyReceipt endpoint desde su aplicación, siempre devolverá la información de actualización más reciente para ese usuario con solo el primer recibo de compra original.

¿Alguien sabe si el uso del método de validación local también devolverá siempre la información más actualizada? ¿O es estrictamente para ese recibo con el que lo valida localmente?

Además, ¿alguien tiene algún consejo para configurar su propio servidor para validar con Apple? No tengo experiencia con eso

@csumrell , puede obtener el recibo de la última compra llamando a getPurchaseHistory y clasificándolo. Los comentarios anteriores detallan cómo hacer esto. Debe tener la información más actualizada.

Y estoy a punto de explorar la opción de validar en el servidor para que podamos ayudarnos mutuamente. Sin embargo, me encantaría saber cómo lo hizo todo el mundo.

@kevinEsherick sí, pero getPurchaseHistory activará la ventana emergente de inicio de sesión de Itunes. Estoy hablando de guardar el recibo original en el almacenamiento local cuando el usuario compra por primera vez. Luego, en lugar de llamar a getPurchaseHistory para verificar la próxima facturación, llame a validateReceiptIos (originalReceipt) que está validado localmente. Sin embargo, no estoy seguro de si validateReceiptIos devuelve las transacciones actualizadas como lo haría el servidor de Apple / verifyReceipt

@csumrell Gotcha. Bueno, eso se puede comprobar rápida y fácilmente. ¿No lo has probado? Si no, lo comprobaré más tarde hoy.
EDITAR: @csumrell validateReceiptIos, de hecho, rastrea las últimas renovaciones automáticas en esa compra, por lo que este es un método válido. Yo diría que esto es mejor que los métodos propuestos anteriormente para verificar las suscripciones porque evita el aviso de inicio de sesión de iTunes (a menos que alguien pueda encontrar una solución para eso). El inconveniente es que debe almacenar los recibos localmente. Esto no debería ser una molestia. Además, es posible que no desee que estos recibos vayan a su backend por razones de seguridad o el hecho de que son masivos (~ 60k caracteres), por lo que significaría que si un usuario suscrito cierra sesión o comienza a usar un nuevo dispositivo, entonces deberá Indíqueles que inicien sesión para obtener su último recibo de compra. Puede almacenar su estado de suscripción internamente para que solo pregunte a los usuarios que se suscribieron la última vez que verificó.

@hyochan ha financiado $ 20.00 para este número. Véalo en IssueHunt

@hyochan, ¿qué solución específica estás financiando? Hemos proporcionado algunos aquí en este hilo, y creo que mi último comentario proporciona la solución más sólida hasta ahora con la menor cantidad de problemas. ¿Está financiando a alguien para que recopile estas soluciones en un PR conciso para el archivo README? ¿O hay un problema de código real que aún desea que se resuelva?

@kevinEsherick

¿Está financiando a alguien para que recopile estas soluciones en un PR conciso para el archivo README?

Si. Esto es absolutamente correcto. Además, solo quería probar cómo issue hunt podría funcionar de inmediato. Mi próximo plan es financiar la realización de casos de prueba.

@kevinEsherick Tiene razón, la validación local con validateReceiptIos devuelve de hecho la información más reciente sobre la suscripción utilizando solo el ID de la primera transacción original (almacenado en estado redux)

Usaré este método para evitar tener que configurar mi propio servidor y para evitar la ventana emergente de inicio de sesión de iTunes mientras verifico si la suscripción está activa.

Una nota para aquellos que estén considerando usar este flujo de validación que @csumrell y yo hemos presentado: descubrí que en el desarrollo, el indicador de inicio de sesión de iTunes sigue apareciendo en el primer lanzamiento. Esto no sucede en producción. Siempre que siga los pasos que hemos establecido, todo debería estar bien. En cuanto a por qué está haciendo esto, tal vez cuando iOS ve que se está usando IAP / es parte del mapa del módulo en desarrollo, automáticamente solicita iniciar sesión. No estoy muy seguro, pero parece ser solo una característica de las pruebas de sandbox.

Hola, parece que no ha habido actividad sobre este tema recientemente. ¿Se ha solucionado el problema o aún requiere la atención de la comunidad? Este problema puede resolverse si no se produce más actividad. También puede etiquetar este problema como "Para discusión" o "Buen primer número" y lo dejaré abierto. Gracias por sus aportaciones.

Cerrando este problema después de un período prolongado de inactividad. Si este problema todavía está presente en la última versión, no dude en crear un nuevo problema con información actualizada.

¿Quizás todo este flujo necesita aclararse en la documentación?

¿Especialmente cuando se trata de la suscripción de renovación automática? No está muy claro para un recién llegado si puede verificar el expires_date_ms para ver si la suscripción aún está activa o no.

Sugiero que el mejor lugar para esto sería en un ejemplo de trabajo más completo.

¿Estaría interesada la gente en esto? Si tengo algo de tiempo, ¿podría trabajar en esto?

@alexpchin sí, eso definitivamente ahorraría una tonelada de tiempo de desarrollo y tu karma se limpiará como el cristal

¡Estar de acuerdo! ¿Alguien estaría interesado en el # 856?

Hola,

El método de @andrewzey nos ha funcionado muy bien hasta ayer, cuando de repente ya no teníamos que validar los recibos de nuestras suscripciones; recibimos un error de análisis JSON de iOS al llamar a validateReceiptIos (). ¿Supongo que nadie más se ha encontrado con esto ...? El único cambio que se realizó desde ayer fue la adición de un código de promoción en ITC que desde entonces hemos eliminado para ayudar a eliminar las variables. ¿Hay alguna razón por la que un recibo deba fallar en el análisis JSON? Falla para cada recibo devuelto, no solo el recibo en el índice 0 de sortedAvailablePurchases. El código es básicamente idéntico al ejemplo de andrewzey: he omitido todo después de validateReceiptIos ya que el error aparece allí.

Estamos ejecutando RN 0.61.4, RNIap: 4.4.2 e iOS 13.3.1

Lo hemos intentado:

  • Reinstalando la aplicación
  • Nueva cuenta de usuario
  • Usando un nuevo secreto de aplicación
  • Prueba de todos los recibos de cada usuario: no se puede analizar ni un solo recibo

Nos preguntamos si no estábamos usando finishTransactionIOS correctamente cuando hicimos las compras de la caja de arena.

Lo que es realmente extraño es que cuando validamos el recibo con esta herramienta en línea , todo parece normal y podemos ver todos los metadatos del recibo.

  isSubscriptionActive = async () => {
      const availablePurchases = await RNIap.getAvailablePurchases();
      const sortedAvailablePurchases = availablePurchases.sort(
        (a, b) => b.transactionDate - a.transactionDate
      );
      const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;
      const isTestEnvironment = __DEV__;

      try {
        const decodedReceipt = await RNIap.validateReceiptIos(
          {
            'receipt-data': latestAvailableReceipt,
            password: Config.IN_APP_PURCHASE_SECRET,
          },
          isTestEnvironment
        );
        console.log('response!', decodedReceipt)
      } catch (error) {
        console.warn('Error validating receipt', error) // JSON PARSE ERROR HERE
      }
...

Pregunta sobre el código que colocó @andrewzey . ¿No sería Date.now() la hora actual del dispositivo y un usuario podría cambiar esto y tener una suscripción sin fin? 🤔

Además, ¿no se desaconseja verificar el recibo desde la propia aplicación?

@doteric ¿Podrías señalarme dónde mencionan que se desaconseja? Sé que puede configurar notificaciones del lado del servidor, pero parece mucho más fácil desde mi perspectiva manejar esta validación en el cliente en lugar de en el servidor.
Estoy luchando por encontrar la documentación correcta de Apple u otras fuentes nativas sólidas de reacción.

@doteric sí Date.now () es la hora del dispositivo, por lo que podría solucionarse. Sin embargo, las posibilidades de que un usuario haga esto son minúsculas, e incluso se podrían solucionar otros métodos para obtener una suscripción sin fin. Por ejemplo, si usa una validación simple del lado del servidor, podrían ingresar al modo avión antes de abrir la aplicación para evitar que la aplicación se dé cuenta de que la suscripción ha caducado. Por supuesto, hay otras protecciones que podría implementar, pero mi punto es que el lado del cliente que usa Date.now () es ciertamente funcional. @captaincole No tengo los documentos a mano, pero puedo confirmar que yo también he leído que se desaconseja la validación del lado del cliente. Lo leí en los documentos de Apple, creo. Dicho esto, creo que el lado del cliente hace el trabajo.

Hola,

Estaba probando uno de los enfoques discutidos en este hilo, usando validateReceiptIos y latest_receipt_data (comparando expires_date_ms con la fecha real). Funcionó muy bien mientras se desarrollaba en Xcode. Sin embargo, cuando lo probé en Testflight ya no funcionó. Es difícil de depurar, así que no puedo identificar el problema exacto, parece que no está obteniendo los datos del recibo. ¿Alguien tuvo un problema similar con testflight?

¡Gracias por adelantado!

Hola,

Estaba probando uno de los enfoques discutidos en este hilo, usando validateReceiptIos y latest_receipt_data (comparando expires_date_ms con la fecha real). Funcionó muy bien mientras se desarrollaba en Xcode. Sin embargo, cuando lo probé en Testflight ya no funcionó. Es difícil de depurar, así que no puedo identificar el problema exacto, parece que no está obteniendo los datos del recibo. ¿Alguien tuvo un problema similar con testflight?

¡Gracias por adelantado!

+1

@asobralr @ Somnus007 Puede que me equivoque, pero no creo que pueda probar IAP con testflight ya que los usuarios no pueden realizar compras a través de testflight. Apple no ofrece grandes oportunidades para probar compras en entornos de producción.

@asobralr @ Somnus007 Puede que me equivoque, pero no creo que pueda probar IAP con testflight ya que los usuarios no pueden realizar compras a través de testflight. Apple no ofrece grandes oportunidades para probar compras en entornos de producción.

Correcto. Ni siquiera puede simular escenarios de falla en la caja de arena, lo cual es una pena. 😞

Hola @kevinEsherick , gracias por tu respuesta. Puede que te equivoques. El usuario puede realizar compras a través de teslflight. En testflight, parece que el usuario tiene que iniciar sesión en una cuenta de zona de pruebas que tiene la misma dirección de correo electrónico que su cuenta real de Apple. Entonces todo funciona bien. Por cierto, ¿ getPurchaseHistory activará la ventana emergente de inicio de sesión de Itunes en el

Parece que ios 14 rompe esta solución debido al hecho de que hay problemas cuando llamas a getAvailablePurchases () antes de llamar a requestSubscription ().

¿Fue útil esta página
0 / 5 - 0 calificaciones