
Uncategorized @pt
September 7, 2022
Updated: March 20, 2023
Validação no servidor (validação do recibo do lado do servidor (server-side)) é uma forma de verificar a autenticidade da compra. Ao contrário da validação baseada no dispositivo, a validação no servidor ocorre – aguarde – no lado do servidor. A validação significa que o dispositivo ou o servidor faz uma solicitação aos servidores da Apple para descobrir se a compra realmente ocorreu e se é válida.
Deve-se salientar que a validação no servidor não é obrigatória — as compras no aplicativo ainda serão realizadas sem ela. No entanto, ela confere algumas vantagens:
Segundo nossa experiência, a primeira vantagem, por si só, já é suficiente para configurar o processamento de compras no servidor.
Em geral, o processo de validação de recibos no iOS é assim:
Para enviar um pedido de validação de pagamento, é necessário incluir o segredo compartilhado para autorizar o pedido. Você pode gerar um segredo no App Store Connect.
O segredo compartilhado pode ser criado para um aplicativo específico (segredo específico do aplicativo) ou para todos os aplicativos da conta (segredo primário).
Para gerar um segredo específico para o aplicativo, abra a página do aplicativo na App Store Connect, acesse Compras no aplicativo → Gerenciar e clique em Segredo compartilhado específico para o aplicativo. Na janela que se abre, é possível gerar um novo token ou copiar o token existente.
Para receber o segredo para todos os aplicativos em sua conta, abra a página Usuários e Acesso e selecione a aba Segredo Compartilhado.
Assim que você receber o segredo compartilhado, pode-se enviar os recibos para serem validados nos servidores da Apple. Para isso, é necessário fazer uma solicitação verifyReceipt. Você deve enviar uma solicitação POST para https://buy.itunes.apple.com/verifyReceipt. No corpo do JSON do pedido, digite o segredo compartilhado no campo. password e o recibo no campo receipt-data. Também existe o parâmetro opcional exclude-old-transactions . Caso tenha o valor true value, para cada assinatura renovável automaticamente, você receberá apenas a última transação ao invés do histórico completo de renovação.
Veja a seguir qual é a carga útil da solicitação de uma validação de compra:
{
"password": "f4d35830e3...52aae",
"receipt-data": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"exclude-old-transactions": false
}
Caso você esteja trabalhando no ambiente Sandbox – ou seja, esteja testando as compras, envie solicitações de validação para https://sandbox.itunes.apple.com/verifyReceipt. O segredo compartilhado, assim como a carga útil e os formatos de resposta permanecem os mesmos.
É importante destacar que não será possível validar um recibo criado no ambiente Sandbox em um servidor de Produção, e vice-versa. É por isso que em sistemas do mundo real, a melhor prática é direcionar a primeira solicitação ao servidor de Produção e redirecioná-la para o servidor Sandbox caso a chave de status retorne o código de erro 21007. Este comportamento é obrigatório durante a revisão do aplicativo, pois permite que os funcionários da Apple testem as compras e que os usuários reais do seu aplicativo as façam.
Entre outros erros a serem observados, há o código de erro 21004 que significa que estamos usando o segredo errado. É importante rastrear, já que isso tem um impacto não só na experiência do usuário mas também na precisão do processo de analytics. Na pior das hipóteses, o aplicativo pode ser removido da App Store caso o usuário não tenha acesso aos recursos premium depois de pagar por eles.
Caso a validação tenha sido realizada com sucesso (status=0), a resposta deve conter os detalhes das transações do usuário.
Veja a seguir a resposta à solicitação de validação do pagamento:
{
"environment": "Production",
"receipt": {
"receipt_type": "Production",
"adam_id": 123,
"app_item_id": 123,
"bundle_id": "com.adapty.sample_app",
"application_version": "1",
"download_id": 123,
"version_external_identifier": 123,
"receipt_creation_date": "2021-04-28 19:42:01 Etc/GMT",
"receipt_creation_date_ms": "1619638921000",
"receipt_creation_date_pst": "2021-04-28 12:42:01 America/Los_Angeles",
"request_date": "2021-08-09 18:26:02 Etc/GMT",
"request_date_ms": "1628533562696",
"request_date_pst": "2021-08-09 11:26:02 America/Los_Angeles",
"original_purchase_date": "2017-04-09 21:18:41 Etc/GMT",
"original_purchase_date_ms": "1491772721000",
"original_purchase_date_pst": "2017-04-09 14:18:41 America/Los_Angeles",
"original_application_version": "1",
"in_app": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED"
}
]
},
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
],
"status": 0
}
A resposta é bastante complicada e foi simplificada na nova versão API do servidor da App Store, mas a implementação atual não é tão difícil de se obter.
Para verificar se o usuário tem acesso aos recursos premium do aplicativo, você precisa definir uma maneira de determinar o status da assinatura. Não há nenhuma solicitação dedicada para recuperar o status da assinatura na versão atual da API, portanto, você deve trabalhar com o histórico de transações em qualquer caso.
Por padrão, a matriz latest_receipt_info contém todas as transações de compra de um usuário específico, exceto aquelas para produtos consumíveis que são concluídas no lado do aplicativo. Desta forma, é possível recuperar todo o histórico de compras do usuário. Este procedimento é bastante útil para o processo de analytics e para determinar o status atual da assinatura.
Ao que parece, sempre as transações mais recentes são classificadas em primeiro lugar. De qualquer modo, recomendo que você faça a implementação de uma classificação por data para as transações.
A carga útil da transação:
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "1000000831360853",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1619638918000",
"purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-05-05 19:41:58 Etc/GMT",
"expires_date_ms": "1620243718000",
"expires_date_pst": "2021-05-05 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000397200750",
"is_trial_period": "true",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
Para verificar o atual status da assinatura, basta acessar a última transação da cadeia e analisar a propriedade expires_date. Uma exceção importante seria o período de carência, que discutiremos posteriormente.
Em termos analíticos, recomendo ter em mente as seguintes propriedades:
A Apple anunciou na WWDC 2021 que está planejando adicionar um campo appAccountToken às informações sobre as transações. Ele deve conter o identificador do usuário no seu sistema. Este identificador deve estar no formato UUID e é definido no lado do aplicativo quando a compra é iniciada. Caso seja definido, ele retornará em todas as transações desta cadeia (renovações de assinatura, problemas de cobrança, etc.), o que significa que ficará mais fácil para você entender qual usuário fez a compra.
Você também deve rastrear o parâmetro subscription_group_identifier . Caso o usuário tenha realizado anteriormente alguma transação em períodos de avaliação ativos ou com ofertas iniciais, ele não deve ter acesso a estas transações no mesmo grupo de assinaturas. Deve-se rastrear este aspecto no lado do servidor.
A matriz pending_renewal_info armazena os dados de renovação da assinatura. Permite entender o que acontecerá com a assinatura no próximo período de cobrança. Por exemplo, caso você verifique que o usuário optou pela não renovação automática, é possível sugerir que ele migre para um plano diferente ou também apresentar uma oferta promocional. São eventos que podem ser rastreados manualmente através de notificações do servidor, que discutirei em breve.
Data de renovação da assinatura:
{
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853",
"auto_renew_status": "1"
}
Caso o usuário não tenha produtos de renovação automática, as chaves latest_receipt_info pending_renewal_info não serão retornadas. Neste caso, as transações podem ser localizadas no receipt → in_app. O formato da transação é similar ao das transações de renovação automática, mas não tem campos para expiração, renovações, ofertas e outras propriedades exclusivas para transações de renovação automática.
Deve-se notar que o receipt → in_app também aparecerá para transações de renovação automática, mas a melhor prática é usar o latest_receipt_info, pois os dados de assinatura que ele contém serão os mais atualizados.
Algum tempo atrás, você teria que desenvolver um sistema complexo para acompanhar as mudanças no status da assinatura. Por exemplo, para entender se a assinatura foi ou não renovada, você teria que enviar um pedido de status de assinatura para os servidores da Apple por hora com início 24 horas antes do vencimento da assinatura. A Apple adicionava mais e mais notificações de servidor ao longo do tempo, e estas notificações agora abrangem praticamente todos os eventos importantes relacionados com as assinaturas. Trata-se de algo muito útil: quando houver alguma mudança do lado da Apple, você receberá uma notificação a respeito no seu próprio servidor.. Ou seja, você receberá notificações sobre novas compras, renovações de assinatura, problemas de cobrança, etc. Isto permite que você colete dados de analytics muito mais precisos, assim como facilita muito o gerenciamento do status da assinatura.
Você pode habilitar as notificações do servidor no App Store Connect. Abra a página do aplicativo e acesse Geral -> Informações do aplicativo. Em seguida, coloque o link no campo URL para notificações do servidor da App Store e salve as alterações.
Notificação do servidor:
{
"notification_type": "DID_RENEW",
"password": "f4d35830e3...52aae",
"environment": "PROD",
"auto_renew_product_id": "basic_subscription_1_month",
"auto_renew_status": "true",
"unified_receipt": {
"status": 0,
"environment": "Production",
"latest_receipt_info": [
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001020690335",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-08-04 19:41:58 Etc/GMT",
"purchase_date_ms": "1628106118000",
"purchase_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-11 19:41:58 Etc/GMT",
"expires_date_ms": "1628710918000",
"expires_date_pst": "2021-08-11 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000438372383",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
},
{
"quantity": "1",
"product_id": "basic_subscription_1_month",
"transaction_id": "230001017218955",
"original_transaction_id": "1000000831360853",
"purchase_date": "2021-07-28 19:41:58 Etc/GMT",
"purchase_date_ms": "1627501318000",
"purchase_date_pst": "2021-07-28 12:41:58 America/Los_Angeles",
"original_purchase_date": "2021-04-28 19:41:58 Etc/GMT",
"original_purchase_date_ms": "1619638918000",
"original_purchase_date_pst": "2021-04-28 12:41:58 America/Los_Angeles",
"expires_date": "2021-08-04 19:41:58 Etc/GMT",
"expires_date_ms": "1628106118000",
"expires_date_pst": "2021-08-04 12:41:58 America/Los_Angeles",
"web_order_line_item_id": "230000849023623",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "272394410"
}
],
"latest_receipt": "MIIUVQY...4rVpL8NlYh2/8l7rk0BcStXjQ==",
"pending_renewal_info": [
{
"auto_renew_status": "1",
"auto_renew_product_id": "basic_subscription_1_month",
"product_id": "basic_subscription_1_month",
"original_transaction_id": "1000000831360853"
}
]
},
"bid": "com.adapty.sample_app",
"bvrs": "0"
}
O formato da notificação do servidor é similar ao da resposta de validação de pagamento. Os detalhes da transação são armazenados em unified_receipt → latest_receipt_info. A chave do password armazena o segredo compartilhado do seu aplicativo, permitindo que você verifique a autenticidade da solicitação. A chave notification_type armazena o tipo de evento. Na minha opinião, as mais úteis são:
A validação do servidor pode sobrecarregar seu processo de analytics dos dados que você coleta no seu aplicativo. Também dificulta o acesso de fraudadores ao seu conteúdo premium, além de permitir que você faça suas assinaturas em diferentes plataformas. Ao mesmo tempo, a implementação da validação do servidor pode consumir muito tempo, especialmente se você precisar de dados extremamente precisos. Isto exigiria levar em conta muitos casos paralelos: upgrade de assinatura, crossgrade de assinatura, períodos de avaliação, ofertas promocionais/iniciais, períodos de carência, reembolsos, assinaturas familiares, etc. Você também deve ter conhecimento e considerar algumas diferenças, por exemplo, a Apple tem uma política de reduzir a comissão de 30% para 15% para assinaturas que são renovadas regularmente por mais de um ano.
Further reading
Uncategorized @pt
September 7, 2022