Usar recibos para comprobar la compra de productos
Cada transacción de Microsoft Store que da como resultado una compra correcta del producto puede devolver opcionalmente un recibo de transacción. Este recibo proporciona información sobre el producto enumerado y el costo monetario para el cliente.
Tener acceso a esta información admite escenarios en los que la aplicación necesita comprobar que un usuario compró la aplicación o ha realizado compras de complemento (también denominadas productos desde la aplicación o IAP) desde Microsoft Store. Por ejemplo, imagine un juego que ofrece contenido descargado. Si el usuario que compró el contenido del juego quiere reproducirlo en un dispositivo diferente, debe comprobar que el usuario ya posee el contenido. Esta es la manera de hacerlo.
Importante
En este artículo se muestra cómo usar miembros del espacio de nombres Windows.ApplicationModel.Store para obtener y validar un recibo de una compra desde la aplicación. Si usas el espacio de nombres Windows.Services.Store para compras desde la aplicación (introducidos en Windows 10, versión 1607 y disponibles para proyectos destinados a Windows 10 Anniversary Edition (10.0; Compilación 14393) o una versión posterior en Visual Studio), este espacio de nombres no proporciona una API para obtener recibos de compra para las compras desde la aplicación. Sin embargo, puedes usar un método REST en la API de recopilación de Microsoft Store para obtener datos de una transacción de compra. Para obtener más información, consulta Recibos para compras desde la aplicación.
Solicitud de un recibo
El espacio de nombres Windows.ApplicationModel.Store admite varias maneras de obtener un recibo:
- Al realizar una compra mediante CurrentApp.RequestAppPurchaseAsync o CurrentApp.RequestProductPurchaseAsync (o una de las otras sobrecargas de este método), el valor devuelto contiene el recibo.
- Puedes llamar al método CurrentApp.GetAppReceiptAsync para recuperar la información de recibo actual de la aplicación y los complementos de la aplicación.
Un recibo de aplicación tiene un aspecto similar al siguiente.
Nota:
Este ejemplo tiene formato para ayudar a que el XML sea legible. Los recibos de aplicación reales no incluyen espacios en blanco entre 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>
Un recibo de producto tiene este aspecto.
Nota:
Este ejemplo tiene formato para ayudar a que el XML sea legible. Los recibos de productos reales no incluyen espacios en blanco entre 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>
Puede usar cualquiera de estos ejemplos de recibos para probar el código de validación. Para obtener más información sobre el contenido del recibo, vea las descripciones de elementos y atributos.
Validación de un recibo
Para validar la autenticidad de un recibo, necesita el sistema back-end (un servicio web o algo similar) para comprobar la firma del recibo mediante el certificado público. Para obtener este certificado, use la dirección URL https://lic.apps.microsoft.com/licensing/certificateserver/?cid=CertificateId%60%60%60
, donde CertificateId
es el valor CertificateId en el recibo.
Este es un ejemplo de ese proceso de validación. Este código se ejecuta en una aplicación de consola de .NET Framework que incluye una referencia al ensamblado 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());
}
}
}
}
Descripciones de elementos y atributos para un recibo
En esta sección se describen los elementos y atributos de un recibo.
Elemento Receipt
El elemento raíz de este archivo es el elemento Receipt , que contiene información sobre las compras desde la aplicación y desde la aplicación. Este elemento contiene los siguientes elementos secundarios.
Elemento | Obligatorio | Quantity | Descripción |
---|---|---|---|
AppReceipt | No | 0 o 1 | Contiene información de compra para la aplicación actual. |
ProductReceipt | No | 0 o más | Contiene información sobre una compra desde la aplicación para la aplicación actual. |
Signature | Sí | 1 | Este elemento es una construcción XML-DSIG estándar. Contiene un elemento SignatureValue , que contiene la firma que puede usar para validar el recibo y un elemento SignedInfo . |
Receipt tiene los siguientes atributos.
Atributo | Descripción |
---|---|
Versión | Número de versión del recibo. |
CertificateId | Huella digital del certificado usada para firmar el recibo. |
ReceiptDate | Fecha en que se firmó y descargó el recibo. |
ReceiptDeviceId | Identifica el dispositivo usado para solicitar este recibo. |
Elemento AppReceipt
Este elemento contiene información de compra para la aplicación actual.
AppReceipt tiene los atributos siguientes.
Atributo | Descripción |
---|---|
Id | Identifica la compra. |
AppId | Valor de nombre de familia de paquetes que usa el sistema operativo para la aplicación. |
LicenseType | Completo, si el usuario compró la versión completa de la aplicación. Prueba, si el usuario descargó una versión de prueba de la aplicación. |
PurchaseDate | Fecha en que se adquirió la aplicación. |
Elemento ProductReceipt
Este elemento contiene información sobre una compra desde la aplicación para la aplicación actual.
ProductReceipt tiene los atributos siguientes.
Atributo | Descripción |
---|---|
Id | Identifica la compra. |
AppId | Identifica la aplicación a través de la cual el usuario realizó la compra. |
ProductId | Identifica el producto adquirido. |
ProductType | Determina el tipo de producto. Actualmente solo admite un valor de Durable. |
PurchaseDate | Fecha en que se produjo la compra. |