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 coste abonado por el cliente.
Tener acceso a esta información admite escenarios en los que la aplicación necesita comprobar que un usuario ha comprado la aplicación o ha realizado compras de complementos (también denominados productos desde la aplicación o IAP) desde Microsoft Store. Por ejemplo, imagina un juego que ofrece contenido descargado. Si el usuario que compró el contenido quiere jugar en otro dispositivo, debes comprobar que ese usuario ya sea propietario del 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 de las compras desde la aplicación.
Solicitar un recibo
El espacio de nombres Windows.ApplicationModel.Store admite varias formas de obtener un recibo:
- Cuando realizas una compra usando CurrentApp.RequestAppPurchaseAsync o CurrentApp.RequestProductPurchaseAsync (o una de las 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 para tu aplicación y para cualquier complemento de la aplicación.
Un recibo de aplicación tiene la siguiente apariencia.
Nota
En este ejemplo se da 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="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>
Un recibo de producto tiene la siguiente apariencia.
Nota
En este ejemplo se da formato para ayudar a que el XML sea legible. Los recibos de producto reales no incluyen espacios en blanco entre 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>
Puedes usar cualquiera de estos ejemplos de recibo para probar tu código de validación. Para obtener más información sobre el contenido del recibo, consulta las descripciones de elementos y atributos.
Validar un recibo
Para validar la autenticidad de un recibo, necesitas que tu sistema back-end (un servicio web o algo similar) compruebe la firma del recibo usando el certificado público. Para obtener este certificado, usa la dirección URL https://lic.apps.microsoft.com/licensing/certificateserver/?cid=CertificateId%60%60%60
, donde CertificateId
es el valor CertificateId en la recepción.
Este es un ejemplo del 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 de un recibo
Esta sección describe los elementos y atributos de un recibo.
Elemento de recibo
El elemento raíz de este archivo es el elemento Recibo que contiene información sobre la aplicación y compras desde la aplicación. Este elemento contiene los siguientes elementos secundarios.
Elemento | Obligatorio | Cantidad | Descripción |
---|---|---|---|
AppReceipt | No | 0 o 1 | Contiene información de compra de la aplicación actual. |
ProductReceipt | No | 0 o más | Contiene información sobre una compra desde la aplicación de la aplicación actual. |
Firma | Sí | 1 | Este elemento es un estándar de construcción XML DSIG. Contiene un elemento de SignatureValue, que contiene la firma que puedes usar para validar el recibo, y un elemento de SignedInfo. |
Receipt tiene los atributos siguientes.
Atributo | Descripción |
---|---|
Versión | Número de versión del recibo. |
CertificateId | La huella digital de certificado usada para firmar el recibo. |
ReceiptDate | Fecha en la que se firmó y descargo el recibo. |
ReceiptDeviceId | Identifica el dispositivo usado para solicitar este recibo. |
Elemento AppReceipt
Este elemento contiene la información de compra de la aplicación actual.
AppReceipt tiene los atributos siguientes.
Atributo | Descripción |
---|---|
Id | Identifica la compra. |
AppId | El valor de Nombre de familia de paquete que usa el sistema operativo para la aplicación. |
LicenseType | Completa, si el usuario ha comprado la versión completa de la aplicación. Prueba, si el usuario ha descargado una versión de prueba de la aplicación. |
PurchaseDate | Fecha cuando se adquirió la aplicación. |
Elemento ProductReceipt
Este elemento contiene información sobre una compra desde la aplicación de la aplicación actual.
ProductReceipt tiene los atributos siguientes.
Atributo | Descripción |
---|---|
Id | Identifica la compra. |
AppId | Identifica la aplicación a través del cual el usuario realizó la compra. |
ProductId | Identifica el producto comprado. |
ProductType | Determina el tipo de producto. Actualmente solo admite un valor de Duradero. |
PurchaseDate | Fecha cuando se realizó la compra. |
Comentarios
https://aka.ms/ContentUserFeedback.
Próximamente: A lo largo de 2024 iremos eliminando gradualmente GitHub Issues como mecanismo de comentarios sobre el contenido y lo sustituiremos por un nuevo sistema de comentarios. Para más información, vea:Enviar y ver comentarios de