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);
})
})
}
}
}
@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 é:
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 !!
Comentários muito úteis
verifique este