Использование квитанций для проверки покупок продуктов
Каждая транзакция Microsoft Store, которая приводит к успешной покупке продукта, может при необходимости возвращать квитанцию о транзакциях. Эта квитанция содержит сведения о перечисленных продуктах и денежных затратах для клиента.
Доступ к этой информации поддерживает сценарии, в которых приложение должно убедиться, что пользователь приобрел приложение или сделал покупки надстройки (также называемые продуктами в приложении или IAP) из Microsoft Store. Например, представьте игру, которая предлагает скачанный контент. Если пользователь, приобретший игровое содержимое, хочет воспроизвести его на другом устройстве, необходимо убедиться, что пользователь уже владеет содержимым. Это делается следующим образом.
Внимание
В этой статье показано, как использовать элементы пространства имен Windows.ApplicationModel.Store для получения и проверки квитанции для покупки в приложении. Если вы используете пространство имен Windows.Services.Store для покупок в приложении (представлено в Windows 10 версии 1607 и доступно для проектов, предназначенных для Юбилейного выпуска Windows 10 (10.0; Сборка 14393) или более поздней версии в Visual Studio), это пространство имен не предоставляет API для получения квитанций о покупке в приложении. Однако можно использовать метод REST в API сбора Microsoft Store для получения данных для транзакции покупки. Дополнительные сведения см. в разделе "Квитанции" для покупок в приложении.
Запрос квитанции
Пространство имен Windows.ApplicationModel.Store поддерживает несколько способов получения квитанции:
- При покупке с помощью CurrentApp.RequestAppPurchaseAsync или CurrentApp.RequestProductPurchaseAsync (или одной из других перегрузк этого метода), возвращаемое значение содержит квитанцию.
- Вы можете вызвать метод CurrentApp.GetAppReceiptAsync , чтобы получить текущие сведения о квитанции для приложения и любые надстройки в приложении.
Получение приложения выглядит примерно так.
Примечание.
Этот пример отформатирован, чтобы помочь сделать XML-файл доступным для чтения. Реальные квитанции приложений не включают пробелы между элементами.
<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>
Квитанция о продукте выглядит следующим образом.
Примечание.
Этот пример отформатирован, чтобы помочь сделать XML-файл доступным для чтения. Реальные квитанции о продукте не включают пробелы между элементами.
<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>
Для проверки кода проверки можно использовать любой из этих примеров квитанций. Дополнительные сведения о содержимом квитанции см . в описаниях элементов и атрибутов.
Проверка квитанции
Чтобы проверить подлинность квитанции, вам потребуется серверная система (веб-служба или что-то подобное), чтобы проверить подпись квитанции с помощью общедоступного сертификата. Чтобы получить этот сертификат, используйте URL-адрес https://lic.apps.microsoft.com/licensing/certificateserver/?cid=CertificateId%60%60%60
, где CertificateId
значение CertificateId в квитанции.
Ниже приведен пример этого процесса проверки. Этот код выполняется в консольном приложении платформа .NET Framework, которое содержит ссылку на сборку 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());
}
}
}
}
Описания элементов и атрибутов для квитанции
В этом разделе описываются элементы и атрибуты в квитанции.
Элемент Receipt
Корневой элемент этого файла — элемент Receipt , содержащий сведения о покупках приложений и в приложении. Этот элемент содержит следующие дочерние элементы.
Элемент | Обязательное поле | Количество | Description |
---|---|---|---|
AppReceipt | No | 0 или 1 | Содержит сведения о покупке для текущего приложения. |
ProductReceipt | No | 0 и более | Содержит сведения о покупке в приложении для текущего приложения. |
Подпись | Да | 1 | Этот элемент является стандартной конструкцией XML-DSIG. Он содержит элемент SignatureValue , содержащий подпись, которую можно использовать для проверки квитанции и элемента SignedInfo . |
Получение имеет следующие атрибуты.
Атрибут | Description |
---|---|
Версия | Номер версии квитанции. |
CertificateId | Отпечаток сертификата, используемый для подписи квитанции. |
Получение | Дата подписания и скачивания квитанции. |
ReceiptDeviceId | Определяет устройство, используемое для запроса этой квитанции. |
Элемент AppReceipt
Этот элемент содержит сведения о покупке для текущего приложения.
AppReceipt имеет следующие атрибуты.
Атрибут | Описание |
---|---|
Id | Определяет покупку. |
AppId | Значение семейства пакетов, которое использует ос для приложения. |
LicenseType | Полный, если пользователь приобрел полную версию приложения. Пробная версия, если пользователь скачал пробную версию приложения. |
PurchaseDate | Дата приобретения приложения. |
Элемент ProductReceipt
Этот элемент содержит сведения о покупке в приложении для текущего приложения.
ProductReceipt имеет следующие атрибуты.
Атрибут | Описание |
---|---|
Id | Определяет покупку. |
AppId | Определяет приложение, через которое пользователь сделал покупку. |
ProductId | Определяет приобретенный продукт. |
ProductType | Определяет тип продукта. В настоящее время поддерживается только значение устойчивых. |
PurchaseDate | Дата возникновения покупки. |