React-native-iap: Validação de recibo iOS do lado do servidor

Criado em 28 jan. 2020  ·  9Comentários  ·  Fonte: dooboolab/react-native-iap

Estou usando meu servidor de back-end para validar o recibo do iOS. Para isso, tudo o que fiz foi passar o purchase.transactionReceipt para o servidor de aplicativos em uma solicitação de postagem. Quero saber se purchase.transactionReceipt retorna dados de recibo codificados em base64 ou o quê? Estou recebendo o erro: 21002 com a implementação atual

requestServerForReceiptVerification = (purchase: InAppPurchase | SubscriptionPurchase) => {
        const receipt = purchase.transactionReceipt;
        if (receipt) {
            // Hit Server API for Receipt Validation
            if (Platform.OS == 'ios') {
                let headers = {
                    'Content-Type': 'application/json'
                }
                Stores.UserStore.hitVerifyiOSReceiptAPI(receipt, headers, this.dropdown, (response: any) => {
                    finishTransaction(purchase).then(() => {
                        console.warn('Trasaction Finished');
                    }).catch((error) => {
                        console.warn(error.message);
                    })
                })
            } 
        }
    }
📱 iOS 🕵️‍♂️ need more investigation 🚶🏻 stale

Comentários muito úteis

verifique este

const IOS_SHARED_PWD = "********";

async function validateIOS() {
    const latestPurchase = await getLatestPurchase();
    if (latestPurchase) {
        return false;
    }

    return RNIap.validateReceiptIos(
        {
            "receipt-data": latestPurchase.transactionReceipt,
            "password": IOS_SHARED_PWD,
            "exclude-old-transactions": false
        },
        false
    )
        .then(uncheckedValidation => {
            //check test receipt
            if (uncheckedValidation?.status === 21007) {
                return RNIap.validateReceiptIos(
                    {
                        "receipt-data": latestPurchase.transactionReceipt,
                        "password": IOS_SHARED_PWD,
                        "exclude-old-transactions": false
                    },
                    true
                );
            } else {
                return uncheckedValidation;
            }
        })
        .then(checkedValidation => {
            return isValidReceipt(checkedValidation);
        });
}

function getLatestPurchase() {
    return RNIap.getAvailablePurchases().then(purchases => {
        return purchases?.sort((a, b) => Number(b.transactionDate) - Number(a.transactionDate))?.[0] || null;
    });
}

function isValidReceipt(checkedValidation) {
    if (!checkedValidation) {
        return false;
    }

    // check is valid validation request
    if (checkedValidation.status === 21006 || checkedValidation.status === 21010) {
        return false;
    }

    const { latest_receipt_info: latestReceiptInfo } = checkedValidation;
    const latestReceipt = latestReceiptInfo
        ?.sort((a, b) => Number(b.purchase_date_ms) - Number(a.purchase_date_ms))
        ?.find(receipt => receipt.product_id === "some.product.id")?.[0];

    // no receipt
    if (!latestReceipt) {
        return false;
    }

    // refunded receipt
    if (latestReceipt.cancellation_date) {
        return false;
    }

    // expired receipt
    if (Number(latestReceipt.expires_date_ms) < Date.now()) {
        return false;
    }

    return checkedValidation.status === 0;
}

Todos 9 comentários

@hyochan Você pode ajudar aqui, por favor. O recibo da transação que estou recebendo ... é uma string codificada em base 64? Se não, como posso convertê-lo. Estou enviando isso para meu servidor BE, de onde estou acessando a API de validação de recebimento da apple.

Estou investigando o mesmo problema agora. No ios, o transactionReceipt "parece" uma string base64, mas não é. Se eu decodificá-lo no dispositivo e enviá-lo para o console, recebo telas de lixo com palavras aleatórias ocasionais.

Em seguida, tentei enviá-lo para o meu BE Server. Lá, eu decodifiquei usando o núcleo dotnet usando este código:

string base64Decoded;
byte[] data = System.Convert.FromBase64String(receiptString);
base64Decoded = System.Text.Encoding.ASCII.GetString(data);
Console.WriteLine("ios receiptString:");
Console.WriteLine(base64Decoded);

A saída para isso é:
Screen Shot 2020-02-07 at 10 58 37 AM

Meu pensamento é que o recibo tinha cada entrada codificada ... então todo o recibo codificado. Eu esperava encontrar uma resposta aqui antes de começar a testar essa teoria ... mas vou prosseguir e fazer isso.

Eu fiz um pouco de progresso. Nunca consegui fazer com que a função validateReceiptIos retornasse algo que pudesse ser decodificado. Meu pensamento sobre isso é que houve uma mudança na maneira como a Apple o codifica.

Consegui obter informações utilizáveis ​​passando a string bruta para o meu servidor e, em seguida, postando-a no ponto de extremidade verifyReceipt. Usei isso como um guia: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

@leelandclay :
"Consegui obter informações utilizáveis ​​passando a string bruta para o meu servidor e, em seguida, postando-a no ponto de extremidade verifyReceipt."
Você acabou de enviar a string transactionReceipt para o BE?
Eu examinei a Documentação da Apple, mas lá o método de criação dos dados de recibo é para o aplicativo iOS nativo em que obtemos appStoreReceiptURL.

sim. Passe o objeto que você obteve do iap para um servidor, em seguida, envie-o para o site da Web e você receberá de volta um objeto JSON.

OK ... É aqui que estou agora ....

Este é o código que tenho em meu aplicativo:

this.purchaseUpdateSubscription = purchaseUpdatedListener(
  (purchase: InAppPurchase | SubscriptionPurchase) => {
    console.log('purchaseUpdateSubscription called');
    if (purchase) {
      try {
        sendTransactionReceipt(this.state.profile.userId, purchase.transactionReceipt, this.state.token)
          .then(transactionReceipt => {
            finishTransaction(purchase, false)
              .then(finish => {
                this.setState({ transactionReceipt }, () => { this.sendingTransactionReceipt = false; });
              })
              .catch(err => {
                console.log('FinishTransaction ERROR: ' + err);
                this.sendingTransactionReceipt = false;
              });
          })
          .catch(() => {
            this.sendingTransactionReceipt = false;
          });
      } catch (ackErr) {
        console.warn('ackErr', ackErr);
      }
    }
  },
);

No BE Server, simplesmente crio o objeto a seguir e o POST no endpoint / verifyReceipt (usando os locais do servidor na documentação acima).

{
  "receipt-data": <purchase.transactionReceipt received from app>,
  "password": <secret password from within your apple account>,
  "exclude-old-transactions": true
}

Consigo obter respostas com informações válidas. Parte da resposta é uma entrada "latest_receipt" (usada posteriormente). Eu armazeno tudo em meu banco de dados, bem como passo as informações de volta para o aplicativo para que ele possa chamar o finishTransaction e armazenar o recibo válido no estado a ser usado.

Também estou trabalhando em um cron job que será executado uma vez por dia para pegar todas as assinaturas que estão aparecendo como ativas e ultrapassaram a data de expiração. Isso segue o mesmo processo no que diz respeito ao envio das informações para o verifyReceipt. O "recibo-dados" que passo é o "último_receipt" que armazenei da chamada anterior verifyReceipt.

Aparentemente, existe uma maneira estranha com que o sandbox lida com a renovação automática de assinaturas, então estou esperando uma resposta nos Fóruns de Desenvolvedores da Apple sobre o que eu realmente deveria estar procurando.

A situação que vejo é que, depois de comprar uma assinatura, recebo uma resposta que mostra a renovação automática como verdadeira (pending_renewal_info [0] .auto_renew_status de verifyReceipt é mostrado como 1) e as entradas expires_date (há 3) mostram que está no futuro (5 minutos). Se eu esperar até que o tempo expire e chamar verifyReceipt novamente, ele mostrará exatamente os mesmos valores para expires_date e renovação automática.

Meu plano era usar a condição de renovação automática === falso e tempo de expiração <horário atual para determinar se a assinatura deveria ser desativada. Alguém mais viu essa resposta do verifyReceipt ????

verifique este

const IOS_SHARED_PWD = "********";

async function validateIOS() {
    const latestPurchase = await getLatestPurchase();
    if (latestPurchase) {
        return false;
    }

    return RNIap.validateReceiptIos(
        {
            "receipt-data": latestPurchase.transactionReceipt,
            "password": IOS_SHARED_PWD,
            "exclude-old-transactions": false
        },
        false
    )
        .then(uncheckedValidation => {
            //check test receipt
            if (uncheckedValidation?.status === 21007) {
                return RNIap.validateReceiptIos(
                    {
                        "receipt-data": latestPurchase.transactionReceipt,
                        "password": IOS_SHARED_PWD,
                        "exclude-old-transactions": false
                    },
                    true
                );
            } else {
                return uncheckedValidation;
            }
        })
        .then(checkedValidation => {
            return isValidReceipt(checkedValidation);
        });
}

function getLatestPurchase() {
    return RNIap.getAvailablePurchases().then(purchases => {
        return purchases?.sort((a, b) => Number(b.transactionDate) - Number(a.transactionDate))?.[0] || null;
    });
}

function isValidReceipt(checkedValidation) {
    if (!checkedValidation) {
        return false;
    }

    // check is valid validation request
    if (checkedValidation.status === 21006 || checkedValidation.status === 21010) {
        return false;
    }

    const { latest_receipt_info: latestReceiptInfo } = checkedValidation;
    const latestReceipt = latestReceiptInfo
        ?.sort((a, b) => Number(b.purchase_date_ms) - Number(a.purchase_date_ms))
        ?.find(receipt => receipt.product_id === "some.product.id")?.[0];

    // no receipt
    if (!latestReceipt) {
        return false;
    }

    // refunded receipt
    if (latestReceipt.cancellation_date) {
        return false;
    }

    // expired receipt
    if (Number(latestReceipt.expires_date_ms) < Date.now()) {
        return false;
    }

    return checkedValidation.status === 0;
}

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.

@leelandclay Obrigado por apresentar suas soluções. Isso realmente ajudou muito e finalmente pudemos validar nosso recebimento. Obrigado !!

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