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:

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="4e362949-acc3-fe3a-e71b-89893eb4f528">
    <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="4e362949-acc3-fe3a-e71b-89893eb4f528">
    <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.