Usar recibos para verificar compras de produtos
Cada transação da Microsoft Store que resulta em uma compra de produto bem-sucedida pode, opcionalmente, retornar um recibo de transação. Este recibo fornece informações sobre o produto listado e o custo monetário para o cliente.
Ter acesso a essas informações dá suporte a cenários em que seu aplicativo precisa verificar se um usuário comprou seu aplicativo ou fez compras de complemento (também chamado de produto no aplicativo ou IAP) na Microsoft Store. Por exemplo, imagine um jogo que oferece conteúdo baixado. Se o usuário que comprou o conteúdo do jogo quiser jogá-lo em um dispositivo diferente, você precisará verificar se o usuário já possui o conteúdo. Veja aqui como fazer isso.
Importante
Este artigo mostra como usar membros do namespace Windows.ApplicationModel.Store para obter e validar um recibo de uma compra no aplicativo. Se você estiver usando o namespace Windows.Services.Store para compras no aplicativo (introduzido no Windows 10, versão 1607 e disponível para projetos direcionados ao Windows 10 Anniversary Edition (10.0; Build 14393) ou uma versão posterior no Visual Studio), esse namespace não fornece uma API para obter recibos de compra para compras no aplicativo. No entanto, você pode usar um método REST na API de coleção da Microsoft Store para obter dados para uma transação de compra. Para obter mais informações, consulte Recibos de compras no aplicativo.
Solicitando um recibo
O namespace Windows.ApplicationModel.Store oferece suporte a várias maneiras de obter um recibo:
- Quando você faz uma compra usando CurrentApp.RequestAppPurchaseAsync ou CurrentApp.RequestProductPurchaseAsync (ou uma das outras sobrecargas desse método), o valor retornado contém o recibo.
- Você pode chamar o método CurrentApp.GetAppReceiptAsync para recuperar as informações de recibo atuais do seu aplicativo e de todos os complementos em seu aplicativo.
Um recibo de aplicativo é semelhante a este.
Observação
Este exemplo é formatado para ajudar a tornar o XML legível. Os recibos de aplicativos reais não incluem espaços em branco entre os elementos.
<Receipt Version="1.0" ReceiptDate="2012-08-30T23:10:05Z" CertificateId="b809e47cd0110a4db043b3f73e83acd917fe1336" ReceiptDeviceId="0a0a0a0a-1111-bbbb-2222-3c3c3c3c3c3c">
<AppReceipt Id="8ffa256d-eca8-712a-7cf8-cbf5522df24b" AppId="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" PurchaseDate="2012-06-04T23:07:24Z" LicenseType="Full" />
<ProductReceipt Id="6bbf4366-6fb2-8be8-7947-92fd5f683530" ProductId="Product1" PurchaseDate="2012-08-30T23:08:52Z" ExpirationDate="2012-09-02T23:08:49Z" ProductType="Durable" AppId="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" />
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<DigestValue>cdiU06eD8X/w1aGCHeaGCG9w/kWZ8I099rw4mmPpvdU=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>SjRIxS/2r2P6ZdgaR9bwUSa6ZItYYFpKLJZrnAa3zkMylbiWjh9oZGGng2p6/gtBHC2dSTZlLbqnysJjl7mQp/A3wKaIkzjyRXv3kxoVaSV0pkqiPt04cIfFTP0JZkE5QD/vYxiWjeyGp1dThEM2RV811sRWvmEs/hHhVxb32e8xCLtpALYx3a9lW51zRJJN0eNdPAvNoiCJlnogAoTToUQLHs72I1dECnSbeNPXiG7klpy5boKKMCZfnVXXkneWvVFtAA1h2sB7ll40LEHO4oYN6VzD+uKd76QOgGmsu9iGVyRvvmMtahvtL1/pxoxsTRedhKq6zrzCfT8qfh3C1w==</SignatureValue>
</Signature>
</Receipt>
Um recibo de produto tem esta aparência.
Observação
Este exemplo é formatado para ajudar a tornar o XML legível. Os recebimentos de produtos reais não incluem espaços em branco entre os elementos.
<Receipt Version="1.0" ReceiptDate="2012-08-30T23:08:52Z" CertificateId="b809e47cd0110a4db043b3f73e83acd917fe1336" ReceiptDeviceId="0a0a0a0a-1111-bbbb-2222-3c3c3c3c3c3c">
<ProductReceipt Id="6bbf4366-6fb2-8be8-7947-92fd5f683530" ProductId="Product1" PurchaseDate="2012-08-30T23:08:52Z" ExpirationDate="2012-09-02T23:08:49Z" ProductType="Durable" AppId="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" />
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<DigestValue>Uvi8jkTYd3HtpMmAMpOm94fLeqmcQ2KCrV1XmSuY1xI=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>TT5fDET1X9nBk9/yKEJAjVASKjall3gw8u9N5Uizx4/Le9RtJtv+E9XSMjrOXK/TDicidIPLBjTbcZylYZdGPkMvAIc3/1mdLMZYJc+EXG9IsE9L74LmJ0OqGH5WjGK/UexAXxVBWDtBbDI2JLOaBevYsyy+4hLOcTXDSUA4tXwPa2Bi+BRoUTdYE2mFW7ytOJNEs3jTiHrCK6JRvTyU9lGkNDMNx9loIr+mRks+BSf70KxPtE9XCpCvXyWa/Q1JaIyZI7llCH45Dn4SKFn6L/JBw8G8xSTrZ3sBYBKOnUDbSCfc8ucQX97EyivSPURvTyImmjpsXDm2LBaEgAMADg==</SignatureValue>
</Signature>
</Receipt>
Você pode usar qualquer um desses exemplos de recibo para testar seu código de validação. Para obter mais informações sobre o conteúdo do recibo, consulte as descrições de elementos e atributos.
Validando um recibo
Para validar a autenticidade de um recibo, você precisa que seu sistema de back-end (um serviço Web ou algo semelhante) verifique a assinatura do recibo usando o certificado público. Para obter esse certificado, use a URL https://lic.apps.microsoft.com/licensing/certificateserver/?cid=CertificateId%60%60%60
, onde CertificateId
é o valor CertificateId no recibo.
Aqui está um exemplo desse processo de validação. Esse código é executado em um aplicativo de console do .NET Framework que inclui uma referência ao assembly System.Security .
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.IO;
using System.Security.Cryptography.Xml;
using System.Net;
namespace ReceiptVerificationSample
{
public sealed class RSAPKCS1SHA256SignatureDescription : SignatureDescription
{
public RSAPKCS1SHA256SignatureDescription()
{
base.KeyAlgorithm = typeof(RSACryptoServiceProvider).FullName;
base.DigestAlgorithm = typeof(SHA256Managed).FullName;
base.FormatterAlgorithm = typeof(RSAPKCS1SignatureFormatter).FullName;
base.DeformatterAlgorithm = typeof(RSAPKCS1SignatureDeformatter).FullName;
}
public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
RSAPKCS1SignatureDeformatter deformatter = new RSAPKCS1SignatureDeformatter(key);
deformatter.SetHashAlgorithm("SHA256");
return deformatter;
}
public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(key);
formatter.SetHashAlgorithm("SHA256");
return formatter;
}
}
class Program
{
// Utility function to read the bytes from an HTTP response
private static int ReadResponseBytes(byte[] responseBuffer, Stream resStream)
{
int count = 0;
int numBytesRead = 0;
int numBytesToRead = responseBuffer.Length;
do
{
count = resStream.Read(responseBuffer, numBytesRead, numBytesToRead);
numBytesRead += count;
numBytesToRead -= count;
} while (count > 0);
return numBytesRead;
}
public static X509Certificate2 RetrieveCertificate(string certificateId)
{
const int MaxCertificateSize = 10000;
// Retrieve the certificate URL.
String certificateUrl = String.Format(
"https://go.microsoft.com/fwlink/?LinkId=246509&cid={0}", certificateId);
// Make an HTTP GET request for the certificate
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(certificateUrl);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
// Retrieve the certificate out of the response stream
byte[] responseBuffer = new byte[MaxCertificateSize];
Stream resStream = response.GetResponseStream();
int bytesRead = ReadResponseBytes(responseBuffer, resStream);
if (bytesRead < 1)
{
//TODO: Handle error here
}
return new X509Certificate2(responseBuffer);
}
static bool ValidateXml(XmlDocument receipt, X509Certificate2 certificate)
{
// Create the signed XML object.
SignedXml sxml = new SignedXml(receipt);
// Get the XML Signature node and load it into the signed XML object.
XmlNode dsig = receipt.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0];
if (dsig == null)
{
// If signature is not found return false
System.Console.WriteLine("Signature not found.");
return false;
}
sxml.LoadXml((XmlElement)dsig);
// Check the signature
bool isValid = sxml.CheckSignature(certificate, true);
return isValid;
}
static void Main(string[] args)
{
// .NET does not support SHA256-RSA2048 signature verification by default,
// so register this algorithm for verification.
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription),
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
// Load the receipt that needs to be verified as an XML document
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("..\\..\\receipt.xml");
// The certificateId attribute is present in the document root, retrieve it
XmlNode node = xmlDoc.DocumentElement;
string certificateId = node.Attributes["CertificateId"].Value;
// Retrieve the certificate from the official site.
// NOTE: For sake of performance, you would want to cache this certificate locally.
// Otherwise, every single call will incur the delay of certificate retrieval.
X509Certificate2 verificationCertificate = RetrieveCertificate(certificateId);
try
{
// Validate the receipt with the certificate retrieved earlier
bool isValid = ValidateXml(xmlDoc, verificationCertificate);
System.Console.WriteLine("Certificate valid: " + isValid);
}
catch (Exception ex)
{
System.Console.WriteLine(ex.ToString());
}
}
}
}
Descrições de elementos e atributos para um recibo
Esta seção descreve os elementos e atributos em um recibo.
Elemento de recebimento
O elemento raiz desse arquivo é o elemento Receipt , que contém informações sobre compras no aplicativo e no aplicativo. Esse elemento contém os seguintes elementos filho.
Elemento | Obrigatório | Quantidade | Descrição |
---|---|---|---|
AppReceipt | Não | 0 ou 1 | Contém informações de compra para o aplicativo atual. |
Recibo do produto | Não | 0 ou mais | Contém informações sobre uma compra no aplicativo para o aplicativo atual. |
Signature | Sim | 1 | Esse elemento é uma construção XML-DSIG padrão. Ele contém um elemento SignatureValue , que contém a assinatura que você pode usar para validar o recibo, e um elemento SignedInfo . |
O recibo tem os seguintes atributos.
Atributo | Descrição |
---|---|
Versão | O número da versão do recibo. |
Identificação do certificado | A impressão digital do certificado usada para assinar o recibo. |
Data de Recebimento | Data em que o recibo foi assinado e baixado. |
ReceiptDeviceId | Identifica o dispositivo usado para solicitar esse recibo. |
Elemento AppReceipt
Esse elemento contém informações de compra para o aplicativo atual.
AppReceipt tem os seguintes atributos.
Atributo | Descrição |
---|---|
Id | Identifica a compra. |
AppId | O valor do Nome da Família de Pacotes que o sistema operacional usa para o aplicativo. |
LicenseType | Completo, se o usuário comprou a versão completa do aplicativo. Avaliação, se o usuário baixou uma versão de avaliação do aplicativo. |
Data de compra | Data em que o aplicativo foi adquirido. |
Elemento ProductReceipt
Esse elemento contém informações sobre uma compra no aplicativo para o aplicativo atual.
ProductReceipt tem os seguintes atributos.
Atributo | Descrição |
---|---|
Id | Identifica a compra. |
AppId | Identifica o aplicativo por meio do qual o usuário fez a compra. |
ProductId | Identifica o produto adquirido. |
ProductType | Determina o tipo de produto. Atualmente, suporta apenas um valor de Durável. |
Data de compra | Data em que a compra ocorreu. |