Dela via


Använda kvitton för att verifiera produktinköp

Varje Microsoft Store-transaktion som resulterar i ett lyckat produktköp kan eventuellt returnera ett transaktionskvitto. Det här kvittot innehåller information om den angivna produkten och den ekonomiska kostnaden för kunden.

Att ha åtkomst till den här informationen stöder scenarier där din app måste verifiera att en användare har köpt din app eller har gjort tilläggsköp (kallas även i appprodukt eller IAP) från Microsoft Store. Tänk dig till exempel ett spel som erbjuder nedladdat innehåll. Om användaren som har köpt spelinnehållet vill spela upp det på en annan enhet måste du kontrollera att användaren redan äger innehållet. Så här gör du.

Viktigt!

Den här artikeln visar hur du använder medlemmar i Windows.ApplicationModel.Store namnrymd för att hämta och verifiera ett kvitto för ett köp i appen. Om du använder Windows.Services.Store namnrymd för köp i appen (introduceras i Windows 10, version 1607 och är tillgänglig för projekt som är inriktade på Windows 10 Anniversary Edition (10.0; Build 14393) eller en senare version i Visual Studio) tillhandahåller inte det här namnområdet något API för att hämta inköpskvitton för köp i appen. Du kan dock använda en REST-metod i Microsoft Store-samlings-API:et för att hämta data för en inköpstransaktion. Mer information finns i Kvitton för köp i appen.

Begära ett kvitto

Windows.ApplicationModel.Store-namnområdet har stöd för flera sätt att få ett kvitto:

Ett appkvitto ser ut ungefär så här.

Anmärkning

Det här exemplet är formaterat för att göra XML-koden läsbar. Verkliga appkvitton innehåller inte blanksteg mellan element.

<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>

Ett produktkvitto ser ut så här.

Anmärkning

Det här exemplet är formaterat för att göra XML-koden läsbar. Riktiga kvitton för produkter innehåller inte mellanslag mellan elementen.

<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>

Du kan använda något av dessa kvittoexempel för att testa verifieringskoden. Mer information om innehållet i kvittot finns i -elementet och attributbeskrivningarna.

Validera ett kvitto

För att verifiera ett kvittos äkthet behöver du ditt serverdelssystem (en webbtjänst eller något liknande) för att kontrollera kvittots signatur med hjälp av det offentliga certifikatet. Om du vill hämta det här certifikatet använder du URL:en https://lic.apps.microsoft.com/licensing/certificateserver/?cid=CertificateId%60%60%60, där CertificateId är värdet CertificateId i kvittot.

Här är ett exempel på valideringsprocessen. Den här koden körs i ett .NET Framework-konsolprogram som innehåller en referens till sammansättningen 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());
            }
        }
    }
}

Beskrivningar av element och attribut för ett kvitto

I det här avsnittet beskrivs elementen och attributen i ett kvitto.

Kvittoelement

Rotelementet i den här filen är elementet Receipt, som innehåller information om app- och appköp. Det här elementet innehåller följande delar.

Komponent Krävs Kvantitet Beskrivning
AppReceipt Nej 0 eller 1 Innehåller köpinformation för den aktuella appen.
Produktkvitto Nej 0 eller mer Innehåller information om ett köp i appen för den aktuella appen.
Underskrift Ja 1 Det här elementet är en standardkonstruktion XML-DSIG. Den innehåller ett SignatureValue--element som innehåller signaturen som du kan använda för att verifiera kvittot och ett SignInfo--element.

Kvitto har följande attribut.

Egenskap Beskrivning
Version: Versionsnumret för kvittot.
CertifikatId Certifikatets tumavtryck som användes för att signera kvittot.
Kvittodatum Datum då kvittot signerades och laddades ned.
KvittoEnhetId Identifierar den enhet som används för att begära det här kvittot.

AppReceipt-elementet

Det här elementet innehåller köpinformation för den aktuella appen.

AppReceipt har följande attribut.

Egenskap Beskrivning
ID Identifierar köpet.
AppId Värdet Paketfamiljenamn som operativsystemet använder för appen.
Licenstyp Fullständigom användaren har köpt den fullständiga versionen av appen. Utvärderingsversion, om användaren laddade ned en utvärderingsversion av appen.
PurchaseDate Datum då appen förvärvades.

Produktkvittoelement

Det här elementet innehåller information om ett köp i appen för den aktuella appen.

ProductReceipt har följande attribut.

Egenskap Beskrivning
ID Identifierar köpet.
AppId Identifierar den app genom vilken användaren gjorde köpet.
Produkt-ID Identifierar den köpta produkten.
Produkttyp Avgör produkttypen. Stöder för närvarande endast värdet Durable.
PurchaseDate Datum då köpet inträffade.