React-native-iap: IOS verificar se a assinatura de renovação automática ainda está ativa

Criado em 27 set. 2018  ·  61Comentários  ·  Fonte: dooboolab/react-native-iap

Versão do react-native-iap

2.2.2

Plataformas em que você enfrentou o erro (IOS ou Android ou ambos?)

IOS

Comportamento esperado

Usando a API RNIAP, podemos verificar se uma assinatura de renovação automática ainda está ativa

Comportamento real

No android, estou fazendo isso para verificar se:

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;
        }
    }
}

No ios não há flag para verificar isso, e o método getAvailablePurchases retorna todas as compras feitas, mesmo as assinaturas que não estão ativas no momento.

Existe uma maneira de verificar isso?

Cumprimentos,
Marcos

📱 iOS 🚶🏻 stale

Comentários muito úteis

Aqui está a função que estou usando que funciona no Android e no iOS, classificando corretamente no iOS para garantir que recebamos os dados de recibo mais recentes:

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 comentários

Estou trabalhando para descobrir isso também. Eu não testei ainda, mas pelo que estou lendo, estou concluindo que é possível criar um "segredo compartilhado" no iTunes Connect e passá-lo para validateReceiptIos com a chave 'senha'. Acredito que isso retornará um objeto JSON que conterá um código indicando o status de validação da assinatura e as chaves latest_receipt, latest_receipt_info e latest_expired_receipt info, entre outros, que você pode usar para determinar o status da assinatura. Estou literalmente apenas descobrindo isso, então ainda não testei. É o que estou reunindo a partir dos problemas e dos documentos da Apple. Se isso funcionar, realmente deve ficar bem claro na documentação, em vez de ser enterrado nos problemas.
Acredito que os links a seguir sejam relevantes:
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

203 # 237

EDIT : Posso confirmar que o processo que mencionei acima funciona. Acho que devemos trabalhar para obter uma explicação completa sobre isso nos documentos. Eu implementaria algo assim no lançamento do aplicativo para determinar se uma assinatura expirou:

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

Brilhante!

Apenas algumas coisas que não são tão triviais sobre o seu código;

  1. Deveria ser

const expiration = renewalHistory [renewalHistory.length - 1] .expires_date_ms

em vez de (provavelmente apenas um erro de digitação)

const expiration = latestRenewalReceipt [latestRenewal.length - 1] .expires_date_ms

2) Para quem não sabe o que é 'senha': 'qualquer que seja sua senha':

Você pode criar sua chave secreta compartilhada para seus aplicativos no aplicativo e usá-la em seu aplicativo

As etapas estão aqui: https://www.appypie.com/faqs/how-can-i-get-shared-secret-key-for-in-app-purchase

+1 para atualizar os docs, também, talvez @dooboolab você pode separar os casos de uso em IOS e Android parece que tem algumas coisas diferentes. Eu posso te ajudar se você quiser.

Cumprimentos

@kevinEsherick

O que acontece com as assinaturas de renovação automática?

Parece que expires_date_ms é a primeira data de expiração, não é atualizado

Eu testei isto:

  1. Assine um item de assinatura
  2. Verifique a data de validade (como 5 minutos depois)
  3. Esperar 10 minutos
  4. A assinatura ainda está ativa parece ser uma assinatura de renovação automática
  5. Se eu verificar, o vencimento do último recibo de renovação ainda é o mesmo do ponto 2

Alguma ideia?

Que pena, o prazo de validade parece estar atualizado, só demora um pouco.

Vou deixar este comentário aqui caso alguém esteja no mesmo cenário.

Cumprimentos

  1. Deveria ser

const expiration = renewalHistory [renewalHistory.length - 1] .expires_date_ms

em vez de (provavelmente apenas um erro de digitação)

const expiration = latestRenewalReceipt [latestRenewal.length - 1] .expires_date_ms

Opa, sim, você está correto, foi um erro de digitação. Eu havia mudado o nome para tornar seu significado mais óbvio, mas esqueci de mudar todas as ocorrências. Eu editei, obrigado.

E eu não tive esse cenário exato com data de validade, mas havia algumas coisas com ele não sendo renovada automaticamente, embora de alguma forma resolvido por conta própria (o que me preocupa, mas não consigo reproduzir tão idk o que mais fazer). O problema que você teve é ​​bastante comum. É o comportamento esperado da Apple, então você deve deixar um período de buffer para a renovação da assinatura antes de cancelar a assinatura do usuário. Este problema no SO explica com mais detalhes: https://stackoverflow.com/questions/42158460/autorenewable-subscription-iap-renewing-after-expiry-date-in-sandbox

Obrigado! Ótima informação, pode ser o motivo

Sobre esse assunto, acho que o código @kevinEsherick deveria estar na documentação 👍 É uma ótima maneira de verificar as inscrições!

sim. Eu apreciaria um documento bacana em readme se alguém de vocês solicitar um PR . Obrigado.

Oi pessoal. Eu adicionei a seção de perguntas e respostas no readme. Espero que qualquer um de vocês possa nos dar um PR por esse problema.

Vou fazer uma RP nos próximos dias :)

Também pretendo usar esta biblioteca especificamente para assinaturas. IOS e Android. Documentar a maneira adequada de determinar se um usuário tem uma assinatura válida seria muito apreciado. 😄

A implementação de @curiousdustin da Apple sobre isso é realmente terrível se você se referir ao meio . Como você precisa lidar com esse tipo de coisa em seu back-end, não poderíamos oferecer suporte completo a isso em nosso módulo. No entanto, você pode obter availablePurchases no Android usando nosso método getAvailablePurchases que não funcionará no produto de assinatura ios.

Então você está dizendo que a solução em que @ marcosmartinez7 e @kevinEsherick estão trabalhando neste tópico NÃO é uma solução, e que um servidor de back-end diferente do da Apple é necessário para confirmar o status de uma assinatura do iOS?

Ao ler o artigo do Medium que você postou junto com os documentos da Apple, parece que usar um servidor é preferível, mas não é impossível determinar o status da assinatura apenas com o dispositivo.

Desculpe por ter perdido algumas informações na minha escrita. Vou administrar isso agora.
Apple sugere salvar receipt em seu próprio servidor para evitar ataques intermediários. Portanto, você pode verificar posteriormente o recibo para saber se o recibo é válido ou se a assinatura ainda está disponível.

No entanto, não é impossível ainda tentar com react-native-iap já que fornecemos a busca direta do recibo de validação para o próprio servidor Apple (sandbox e produção) que felizmente @ marcosmartinez7 e @kevinEsherick funcionou.

Eu acho que atualmente esta é a solução para verificar a assinatura de ios .

Olá pessoal, desculpe, já se passaram mais de alguns dias, estou ocupado com o trabalho, mas ainda vou enviar esse PR para atualizar o README. @curiousdustin o método que escrevi acima definitivamente funciona e vou adicioná-lo à documentação. Vou enviar o PR hoje à noite ou amanhã :)

Obrigado pela atualização.

Mais uma coisa que notei em seu exemplo.

'dados de recebimento': compras [compras.length - 1] .transactionReceipt,
...
const expiration = renewalHistory [renewalHistory.length - 1] .expires_date_ms

Em meus testes, as compras e o histórico de renovação não estão necessariamente em uma ordem específica. Não precisaríamos classificá-los ou algo para verificar se estamos usando o que pretendemos?

Mesmo aqui. As compras e o histórico de renovação definitivamente não são solicitados, portanto, temos que classificar primeiro. No dev, isso acaba sendo um monte de iterações.

Aqui está a função que estou usando que funciona no Android e no iOS, classificando corretamente no iOS para garantir que recebamos os dados de recibo mais recentes:

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

No cenário Android, se a assinatura for renovável automaticamente, ela será devolvida nas compras disponíveis

@curiousdustin @andrewzey Você está certo, as compras não foram encomendadas. renovaçãoHistory / latest_receipt_info, no entanto, é sempre solicitado para mim. Lembro-me de perceber que as compras não foram encomendadas, mas pensei ter encontrado algum motivo para que isso não fosse realmente preocupante. Vou cuidar disso em algumas horas, quando chegar em casa. Não quero enviar um PR até que tenhamos resolvido isso.
EDIT: Então, o motivo pelo qual eu não acho que você precisa classificar as compras é porque expires_date_ms retorna o mesmo, independentemente de qual compra você consultou. Isso não é o mesmo para vocês? Ou há alguma informação de que você precisa que requer classificação? Deixe-me saber a sua opinião. Concordo que os documentos ainda devem deixar claro que as compras não são ordenadas cronologicamente, mas, pelo que posso dizer, a classificação não é necessária para obter a data de validade.

@kevinEsherick Obrigado! No meu caso, nem as compras nem o histórico de renovação foram solicitados.

@kevinEsherick @andrewzey Vocês podem dar PR nesta solução? Gostaríamos de compartilhar com outras pessoas que estão sofrendo.

Claro que vou prepará-lo assim que terminarmos de lançar nosso aplicativo público =).

Eu estava esperando por mais conversa sobre meu último comentário. Eu mencionei alguns problemas que permanecem sem solução. Por favor, veja acima para referência. Assim que resolvermos isso, um de nós pode fazer uma RP.

@kevinEsherick Ah, desculpe, eu perdi isso.

expires_date_ms é definitivamente único para mim em cada recibo de renovação. Talvez eu tenha entendido mal? Percebi que a matriz de recibos de renovações anexada a cada recibo decodificado é a mesma, independentemente de qual recibo seja selecionado, então pude ver que você pode estar certo no seguinte (no meu exemplo) não sendo necessário:

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

Mas eu fiz isso de qualquer maneira para tentar ser preciso e compensar possíveis discrepâncias na resposta real da API do que a Apple documenta. Não acho que uma operação de classificação seja muito cara, dado o número total de recibos que você provavelmente encontrará para renovar automaticamente as assinaturas.

Qualquer hora prevista de chegada no PR? Estou pensando em mudar deste pacote, pois ele não trata deste caso.

@schumannd Dado que o PR seria apenas para atualizar os documentos, você pode passar para react-native-iap agora.

Confirmei que meu snippet de código de fato funciona sem problemas na produção com nosso aplicativo lançado recentemente: https://itunes.apple.com/us/app/rp-diet/id1330041267?ls=1&mt=8

EDITAR Com certeza vou fazer o PR (tenho na minha lista "Give back to OSS" no nosso Trello), mas depois da loucura do fim de semana de sexta-feira negra 😄

@schumannd Eu @andrewzey disse. Tudo o que um PR diria está contido aqui neste post. Tenho pensado em dar a volta por cima, mas demorou um pouco para decidirmos exatamente o que precisa ser feito, e isso, misturado com viagens e horas agitadas de inicialização, significa que ainda não tive tempo para fazer relações públicas. Ainda pretendo fazer isso em breve, mas qualquer outra pessoa pode intervir nesse meio tempo, se quiser. E @andrewzey parabéns cara! Parece ótimo, vou fazer um download!

Não estou convencido de que este tópico documente com precisão a maneira correta de gerenciar assinaturas de renovação automática no iOS, mas, em vez disso, descreve um fluxo de trabalho de "solução alternativa" para a API exposta por esta biblioteca. No entanto, isso é inteiramente especulação da minha parte e estou bem ciente de que posso estar errado, então gostaria de receber um feedback sobre as observações que esbocei abaixo.

Tenho pesquisado a maneira correta de gerenciar assinaturas de renovação automática no React Native por semanas, sem sucesso, então mudei de marcha e comecei a pesquisar como gerenciá-las nativamente no Objective-C / Swift. Isso me deu um ponto de referência para comparar a (s) API (s) nativa (s) do StoreKit e o uso documentado com o que foi implementado nesta biblioteca.

Este artigo é a melhor explicação que encontrei até agora sobre como a compra, validação e restauração de assinaturas devem ser tratadas por um aplicativo iOS nativo: https://www.raywenderlich.com/659-in-app-purchases-auto -renewable-subscriptions-tutorial e enquanto todas as APIs estão sendo usadas em react-native-iap , eles estão implementando um fluxo de trabalho diferente.

Uma diferença chave que vejo é o uso de appStoreReceiptUrl .

O StoreKit fornece appStoreReceiptUrl que, no meu entendimento, é a maneira correta de carregar as informações de compra / recibo atuais para um aplicativo iOS. Embora eu veja isso sendo usado e referenciado no código react-native-iap , parece estar em um contexto diferente do fluxo de trabalho documentado no artigo referenciado.

Não tenho certeza se / como essa diferença de implementação é problemática, mas a API react-native-iap parece estar criando uma dependência desnecessária no restoreCompletedTransactions do StoreKit, que é eventualmente chamado de getPurchaseHistory .

Parece-me que a API react-native-iap deve suportar o seguinte fluxo de trabalho para iOS:

  • Tentar carregar o recibo do aplicativo atual (via appStoreReceiptUrl ) na inicialização do aplicativo
  • Fornece uma oportunidade para validar o recibo
  • Se appStoreReceiptUrl for nulo, inicie uma "restauração" no dispositivo atual que definiria o valor de appStoreReceiptUrl e a lógica de validação pode ser repetida

Pensamentos?

Novamente, posso estar interpretando mal a implementação desta biblioteca ou a implementação nativa referenciada.

@sellmeadog

Acho que um ponto-chave que também é mencionado no artigo que você vinculou é este:

Nota: O aplicativo do projeto executa o SelfieService, que aceita recibos e os carrega diretamente para o serviço de validação de recibos da Apple. Este é um “cheat” para manter o tutorial focado na configuração das assinaturas. Em um aplicativo real, você deve usar um servidor remoto para isso - não o aplicativo.

Levei algum tempo para descobrir toda a coisa de assinatura auto-renovável, mas pelo que entendi, a melhor maneira de lidar com isso é usando seu próprio back-end como a única fonte da verdade:

  • enviar todos os dados da transação imediatamente para o seu back-end
  • fornecer alguns meios de reenvio em caso de problemas de transmissão (por exemplo, restauração de compras)
  • periodicamente (por exemplo, uma vez por dia) verifique todos os recibos armazenados em seu back-end
  • o aplicativo consulta o back-end para verificar o estado da assinatura, por exemplo, ao retornar do segundo plano.

Para mim, essa parecia a única maneira sensata de lidar com assinaturas. Eu também verifiquei esse processo com outros desenvolvedores usando assinaturas auto-renováveis ​​comercialmente por algum tempo.

Portanto, minha resposta à pergunta dos OPs seria: verifique as assinaturas periodicamente no back-end, não no front-end

@schumannd

Não tenho certeza se estamos entendendo ou falando sobre o mesmo problema.

A validação do recebimento deve ser feita no servidor. De acordo com a documentação da própria Apple :

Não chame o servidor da App Store /verifyReceipt endpoint do seu aplicativo.

Eu interpretei o "cheat" no artigo referenciado como especificamente as chamadas diretas do aplicativo de amostra para /verifyReceipt diretamente do aplicativo para abreviar, em vez de delegar a um servidor de back-end.

Parece-me que a Apple / a App Store é e deve ser a única fonte da verdade. Em última análise, a Apple está processando compras, renovações, cancelamentos, etc. e gerando recibos de acordo e disponibiliza esses dados por meio das APIs StoreKit .

react-native-iap lida com a compra e restauração de compras sem problemas, mas ainda acho que há uma lacuna no acesso aos dados de recibo da App Store que são disponibilizados localmente em appStoreReceiptUrl que é novamente a forma documentada da Apple de acessar recibos dados :

Para recuperar os dados do recibo, use o método appStoreReceiptURL de NSBundle para localizar o recibo do aplicativo ...

O fluxo de trabalho que entendi lendo os documentos da Apple e fazendo referência a implementações nativas de Object-C / Swift é o seguinte:

  • Carregar produtos para compra
  • Compre um produto (consumível, não consumível, assinatura, não importa)
  • Um recibo dessa compra é armazenado localmente em appStoreReceiptUrl por StoreKit nos bastidores
  • O aplicativo deve enviar dados de recibo para um servidor de back-end (não para a App Store /verifyReceipt diretamente) para validação conforme necessário
  • appStoreReceiptUrl é atualizado por StoreKit sempre que ocorrerem cancelamentos e / ou renovações (assumindo que os delegados StoreKit adequados estão registrados corretamente)
  • Quando appStoreReceiptUrl for nulo, porque o usuário está em um novo dispositivo, etc., use o fluxo de trabalho de restauração de compra que recuperará os dados do recibo atual da Apple / AppStore e StoreKit persistirá localmente em appStoreReceiptUrl nos bastidores

Novamente, react-native-iap trata de tudo isso, exceto para carregar dados de recibo de appStoreReceiptUrl . Acho que um método getReceipt que consultasse o recibo local aliviaria o fardo de gerenciar assinaturas e / ou quaisquer outras compras.

A Apple tornou isso muito mais difícil de raciocinar do que deveria ser.

appStoreReceiptUrl é atualizado por StoreKit sempre que ocorrerem cancelamentos e / ou renovações (assumindo que os delegados StoreKit adequados estejam registrados corretamente)

Isso descreve o problema que tenho com esta abordagem: Você não é notificado sobre qualquer renovação / cancelamento / expiração etc. se o usuário não abrir seu aplicativo depois com uma conexão de Internet em funcionamento.

Fora isso, parece uma boa alternativa à abordagem que descrevi. Eu me pergunto qual é o mais usado para aplicativos de produção.

@sellmeadog Acho que é a mesma ideia que postei aqui: # 356
Validar os recibos localmente é um método, a apple recomenda. Isso é completamente independente de qualquer solicitação de servidor / rede.

Sim. Existem desvantagens em relação a "nenhuma conexão de rede - nenhuma validação".
Mas existem casos de uso para isso.
Quero uma validação rápida do recibo em cada lançamento de aplicativo e não quero controlar as compras no aplicativo por conta própria via UserDefaults ou Keychain . StoreKit já fornece um mecanismo de armazenamento.

Como você deve verificar se o usuário IOS renovou ou cancelou sua assinatura sem solicitar que ele faça login na conta do itunes por meio de algo como getAvailablePurchases?

Por exemplo:
Na primeira compra da assinatura, validamos o recibo e salvamos a data de validade no perfil do firebase. Usamos esse nó do Firebase para verificar na inicialização se eles têm uma assinatura ativa. O problema surge quando a data de validade passou e o novo período de faturamento começa. Como podemos verificar se eles foram renovados automaticamente ou cancelados? Conforme mencionado, estávamos usando getAvailablePurchases, mas isso solicita o login do iTunes como um botão de restauração faria - o que é uma IU terrível

Eu também me pergunto a mesma coisa.

Em nosso aplicativo, não precisamos disso, pois temos uma API do lado do servidor que se comunica diretamente com a Apple para verificar o status. Após a compra original, os dados do recibo são armazenados em nossos servidores e posteriormente utilizados para solicitar a atualização do status.

No entanto, acredito que isso ainda deveria ser possível do lado do dispositivo. Não sou um especialista nisso, mas pelo que entendi, o aplicativo receberá uma atualização das transações / recibos do SO sempre que ocorrer uma renovação. Suspeito que este plugin ESTÁ lidando com essas receitas no lançamento do aplicativo. (Eu vejo um monte de logs ao executar a partir do Xcode, que parecem estar processando várias transações) MAS, pelo que posso dizer, não há como acessar esses eventos no código JS.

Podemos obter mais informações sobre isso?

@csumrell @curiousdustin vocês têm esse comportamento confirmado em um ambiente de produção? Eu me pergunto se isso pode estar ocorrendo apenas por causa do sandbox, com a conta do sandbox precisando fazer login, já que provavelmente há outra conta que não seja do sandbox em seu dispositivo, enquanto na produção o usuário provavelmente estará conectado à conta normal e este pode ser capaz de obter compras sem solicitar login.

Desculpe, NÃO confirmei que getAvailablePurchases() gera solicitações de senha na produção.

Eu apenas presumi que sim porque os documentos falam sobre esse método sendo usado para o recurso comumente referido como Restauração de compras, que na minha experiência envolve autenticação, pelo menos no iOS.

@hyochan @JJMoon Podemos obter mais informações sobre isso também? Especificamente, há uma maneira de obter compras disponíveis ou históricas de maneira confiável sem acionar o sistema operacional para tentar a autenticação? Além disso, consulte meus comentários da pergunta 2 acima, sobre a capacidade de reagir às compras que são detectadas e processadas no lançamento do aplicativo.

@curiousdustin sim, então, quando eu sair da minha conta normal do iTunes e entrar na conta da sandbox, ele não solicitará mais o login quando esse método for chamado. Portanto, meu palpite / esperança é que na produção, onde os usuários não têm uma conta de sandbox em uso, o problema não deve aparecer. O teste beta deve funcionar da mesma forma que a produção, porque os usuários não têm contas sandbox configuradas no dispositivo, é apenas um problema dos dispositivos de desenvolvimento. Vou testar isso amanhã com usuários beta e contarei a vocês o que eu encontrar.
EDIT: Posso confirmar que isso acontece em dispositivos não sandbox. Isso é UX terrível e bastante problemático. Alguém, alguma ideia?

Portanto, quando você valida em seu próprio servidor chamando o endpoint server / verifyReceipt da App Store a partir de seu aplicativo, ele sempre retornará as informações de atualização mais recentes para aquele usuário com apenas o primeiro recibo de compra original.

Alguém sabe se usar o método de validação local também sempre retornará as informações mais atualizadas? Ou é estritamente para esse recibo que você o valida localmente?

Além disso, alguém tem algum conselho para configurar seu próprio servidor para validar com a Apple? Eu não tenho experiência com isso

@csumrell, você pode obter o recibo da última compra ligando para getPurchaseHistory e classificando-o. Os comentários acima detalham como fazer isso. Isso deve ter as informações mais atualizadas.

E estou prestes a explorar a opção de validação no servidor para que possamos ajudar uns aos outros. Adoraria ouvir como qualquer outra pessoa fez isso.

@kevinEsherick sim, mas getPurchaseHistory irá acionar o pop-up de login do Itunes. Estou falando sobre como salvar o recibo original no armazenamento local quando o usuário fizer a primeira compra. Então, em vez de chamar getPurchaseHistory para verificar o próximo faturamento, chamando validateReceiptIos (originalReceipt) que é validado localmente. No entanto, não tenho certeza se validateReceiptIos retorna as transações atualizadas como o apple server / verifyReceipt faria

@csumrell Gotcha. Bem, isso é rápido e facilmente testável. Você ainda não experimentou? Se não, vou verificar mais tarde hoje.
EDITAR: @csumrell validateReceiptIos de fato rastreia as últimas autorenovações nessa compra, portanto, este é um método válido. Eu diria que isso é melhor do que os métodos propostos acima para verificar assinaturas porque evita o prompt de login do iTunes (a menos que alguém possa encontrar uma solução para isso). A desvantagem é que você precisa armazenar os recibos localmente. Isso não deve ser muito incômodo. Além disso, você pode não querer que esses recibos cheguem ao seu back-end por motivos de segurança ou pelo fato de serem enormes (cerca de 60 mil caracteres), então isso significaria que se um usuário inscrito sair ou começar a usar um novo dispositivo, você precisará solicite que façam login para obter o recibo de compra mais recente. Você pode armazenar seu status de assinatura internamente para que você só avise os usuários que foram assinados pela última vez que você verificou.

@hyochan financiou $ 20,00 para esta edição. Veja em IssueHunt

@hyochan que solução específica você está financiando? Fornecemos alguns aqui neste tópico e acho que meu comentário mais recente fornece a solução mais forte com o menor número de problemas. Você está financiando alguém para reunir essas soluções em um PR conciso para o README? Ou existe um problema de código real que você ainda deseja ver resolvido?

@kevinEsherick

Você está financiando alguém para reunir essas soluções em um PR conciso para o README?

sim. Isso está absolutamente certo. Além disso, eu só queria testar como issue hunt poderia funcionar fora da caixa. Meu próximo plano é financiar a realização de casos de teste.

@kevinEsherick Você está correto, a validação local com validateReceiptIos de fato retorna as informações mais recentes sobre a assinatura usando apenas o primeiro ID de transação original (armazenado no estado redux)

Usarei esse método para evitar a necessidade de configurar meu próprio servidor e para evitar o pop-up de login do itunes ao verificar se a assinatura está ativa.

Uma observação para aqueles que estão considerando usar este fluxo de validação que @csumrell e eu estabelecemos: descobri que, no desenvolvimento, o prompt de login do iTunes ainda está aparecendo na primeira inicialização. Isso não acontece na produção. Contanto que você esteja seguindo as etapas que definimos, tudo estará bem. Por que está fazendo isso, talvez quando o iOS vê que o IAP está sendo usado / faz parte do mapa do módulo em desenvolvimento, ele solicita automaticamente o login. Não tenho certeza, mas parece ser apenas um recurso de teste de sandbox.

Olá, parece que não houve nenhuma atividade sobre este problema recentemente. O problema foi corrigido ou ainda requer a atenção da comunidade? Este problema pode ser resolvido se nenhuma outra atividade ocorrer. Você também pode rotular esse problema como "Para discussão" ou "Bom primeiro problema" e vou deixá-lo em aberto. Obrigado por suas contribuições.

Fechar este problema após um período prolongado de inatividade. Se o problema ainda estiver presente na versão mais recente, sinta-se à vontade para criar um novo problema com informações atualizadas.

Talvez todo esse fluxo precise ser esclarecido na documentação?

Especialmente quando se trata de renovação automática da assinatura? Não está claro para um iniciante se você pode verificar expires_date_ms para saber se a assinatura ainda está ativa ou não?

Eu sugiro que o melhor lugar para isso seria em um exemplo de trabalho mais completo?

As pessoas estariam interessadas nisso? Se eu tiver algum tempo, posso trabalhar nisso?

@alexpchin sim, isso definitivamente economizaria muito tempo de desenvolvimento e seu carma ficará limpo como cristal

Aceita! Alguém estaria interessado em # 856?

Oi,

O método de @andrewzey funcionou muito bem para nós até ontem, quando de repente não devíamos mais validar nenhum recibo de nossas assinaturas - estamos recebendo um erro de análise JSON do iOS ao chamar validateReceiptIos (). Acho que ninguém mais encontrou isso ...? A única mudança feita desde ontem foi a adição de um código promocional no ITC que removemos para ajudar a eliminar as variáveis. Existe algum motivo pelo qual um recibo deve falhar na análise JSON? Ele falha para todos os recibos devolvidos - não apenas para os recibos no índice 0 de SortAvailablePurchases. O código é basicamente idêntico ao exemplo de andrewzey - omiti tudo após validateReceiptIos, uma vez que o erro foi gerado.

Estamos executando RN 0.61.4, RNIap: 4.4.2 e iOS 13.3.1

Nós tentamos:

  • Reinstalando aplicativo
  • Nova conta de usuário
  • Usando um novo segredo de aplicativo
  • Teste de todos os recibos de cada usuário - nem um único recibo pode ser analisado

Estamos nos perguntando se não estávamos usando o finishTransactionIOS corretamente quando fizemos as compras do sandbox.

O que é realmente estranho é que quando validamos o recibo usando esta ferramenta online , tudo parece normal e podemos ver todos os metadados do 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
      }
...

Pergunta sobre o código @andrewzey colocado. Date.now() seria a hora atual do dispositivo e um usuário poderia alterar isso e ter uma assinatura infinita? 🤔

Além disso, verificar o recibo do próprio aplicativo não é desencorajado?

@doteric Você poderia me apontar onde eles mencionam que é desencorajado? Eu sei que você pode configurar notificações do lado do servidor, mas parece muito mais fácil, da minha perspectiva, lidar com essa validação no cliente em vez de no servidor.
Estou lutando para encontrar a documentação certa da apple ou outras fontes nativas de reação sólida.

@dotérico sim Date.now () é a hora do dispositivo, então ele poderia ser contornado. No entanto, as chances de um usuário fazer isso são mínimas e até mesmo outros métodos podem ser contornados para uma assinatura infinita. Por exemplo, se estiver usando uma validação simples do lado do servidor, eles podem entrar no modo avião antes de abrir o aplicativo para evitar que o aplicativo perceba que a assinatura expirou. Claro que existem outras proteções que você pode implementar, mas meu ponto é que o lado do cliente usando Date.now () certamente é funcional. @captaincole Não tenho os documentos em mãos, mas posso confirmar que também li que a validação do lado do cliente é desencorajada. Eu li isso nos documentos da Apple, eu acredito. Dito isso, acho que o lado do cliente faz o trabalho.

Oi,

Eu estava tentando uma das abordagens discutidas neste tópico, usando validateReceiptIos e o latest_receipt_data (comparando expires_date_ms com a data real). Funcionou muito bem durante o desenvolvimento em Xcode. No entanto, quando testei no Testflight, ele não funcionou mais. É difícil depurar, então não estou conseguindo identificar o problema exato, parece que não estou obtendo os dados do recibo. Alguém teve um problema semelhante com o testflight?

Desde já, obrigado!

Oi,

Eu estava tentando uma das abordagens discutidas neste tópico, usando validateReceiptIos e o latest_receipt_data (comparando expires_date_ms com a data real). Funcionou muito bem durante o desenvolvimento em Xcode. No entanto, quando testei no Testflight, ele não funcionou mais. É difícil depurar, então não estou conseguindo identificar o problema exato, parece que não estou obtendo os dados do recibo. Alguém teve um problema semelhante com o testflight?

Desde já, obrigado!

+1

@asobralr @ Somnus007 Posso estar errado, mas não acho que você possa testar IAPs com o testflight, pois os usuários não podem fazer compras por meio do testflight. A Apple não oferece grandes oportunidades para testar compras em ambientes semelhantes aos de produção

@asobralr @ Somnus007 Posso estar errado, mas não acho que você possa testar IAPs com o testflight, pois os usuários não podem fazer compras por meio do testflight. A Apple não oferece grandes oportunidades para testar compras em ambientes semelhantes aos de produção

Correto. Você não pode nem simular cenários de falha no sandbox, o que é uma pena. 😞

Olá @kevinEsherick , obrigado pela sua resposta. Você pode estar errado. O usuário pode fazer compras através do teslflight. No testflight, parece que o usuário precisa fazer o login em uma conta sandbox que tenha o mesmo endereço de e-mail da conta real da Apple. Então tudo funciona bem. BTW, getPurchaseHistory irá acionar o pop-up de login do Itunes no ambiente de produção?

Parece que o iOS 14 quebra essa solução devido ao fato de que há problemas ao chamar getAvailablePurchases () antes de chamar requestSubscription ().

Esta página foi útil?
0 / 5 - 0 avaliações