Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Implementare l'in-app purchase

Link copiato negli appunti

Per consentire agli utenti di acquistare funzionalità o contenuti addizionali tramite in-app purchase, è indispensabile progettare la propria app in modo da trattare questi elementi aggiuntivi come moduli separate, in modo da poterli attivare e disattivare in base allo stato della licenza di ciascuno di essi.

Per poter testare l'acquisto di nuovi prodotti tramite in-app purchase, dobbiamo innanzitutto modificare la definizione custom della nostra licenza, includendo informazioni aggiuntive sui prodotti che possono essere acquistati. Il prossimo snippet mostra una nuova definizione XML custom che include un nuovo prodotto acquistabile (la definizione è contenuta in un nuovo file denominato in-app-purchase.xml).

<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
  <ListingInformation>
    <App>
      <AppId>01234567-1234-1234-1234-0123456789AB</AppId>
      <LinkUri>http://apps.microsoft.com/webpdp/app/01234567-1234-1234-1234-0123456789AB</LinkUri>
      <CurrentMarket>en-US</CurrentMarket>
      <AgeRating>3</AgeRating>
      <MarketData xml:lang="en-us">
        <Name>Feature-based Trial</Name>
        <Description>In-app purchase sample</Description>
        <Price>1.00</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </App>
    <Product ProductId="Product001" LicenseDuration="0" ProductType="Durable">
      <MarketData xml:lang="en-us">
        <Name>MyProduct</Name>
        <Price>1.00</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
  </ListingInformation>
  <LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
      <ExpirationDate>2014-06-19T09:00:00.00Z</ExpirationDate>
    </App>
    <Product ProductId="Product001">
      <IsActive>false</IsActive>
    </Product>
  </LicenseInformation>
</CurrentApp>

La proprietà IsTrial dell'app è impostata su false perché il meccanismo di in-app purchase non può essere attivato fino a quando l'applicazione si trova in modalità trial (in questo caso occorre comprare la licenza full dell'app, prima di poter acquistare il prodotto extra). Anche la proprietà IsActive del prodotto è impostata su false, il che significa che questo non è stato ancora acquistato dall'utente.

Dopo aver passato la nuova licenza all'oggetto CurrentAppSimulator, è possibile recuperare le informazioni relative al prodotto, come il nome e il prezzo, tramite il metodo LoadListingInformationAsync. Questo metodo restituisce un dizionario contenente l'elenco di tutti i prodotti che possono essere acquistati tramite in-app purchase. Il prossimo snippet ne mostra un esempio.

private async Task DisplayProductListingInfo()
{
    try
    {
        ListingInformation listingInfo = await CurrentAppSimulator.LoadListingInformationAsync();
        var productListing = listingInfo.ProductListings["Product001"];
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            InAppPurchaseFeatureName.Text = String.Format("Nome del prodotto: {0}", productListing.Name);
            InAppPurchaseFeaturePrice.Text = String.Format("Prezzo: {0}", productListing.FormattedPrice);
        });
    }
    catch (Exception ex)
    {
        Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            InAppPurchaseErrorMessage.Text = String.Format("Impossibile recuperare le informazioni: {0}", ex.Message);
            InAppPurchaseErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
        });
    }
}

Le informazioni relative allo stato della licenza dei prodotti acquistabili tramite in-app purchase possono essere recuperate tramite la proprietà ProductLicense, come mostrato nel prossimo snippet.

private async Task DisplayProductLicenseInfo()
{
    try
    {
        var productLicense = CurrentAppSimulator.LicenseInformation.ProductLicenses["Product001"];
        if (!productLicense.IsActive)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                InAppPurchaseFeatureLicenseStatus.Text = String.Format("Prodotto non attivo. Non puoi usare il prodotto.");
            });
        }
        else
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                InAppPurchaseFeatureLicenseStatus.Text = String.Format("Prodotto attivo. Adesso puoi usarlo.");
            });
        }
    }
    catch (Exception ex)
    {
        Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            InAppPurchaseErrorMessage.Text = String.Format("Impossibile recuperare informazioni sulla licenza: {0}", ex.Message);
        });
    }
}

Per acquistare il prodotto sullo store, è sufficiente sfruttare il metodo RequestProductPurchaseAsync, il quale accetta come parametro una stringa che accetta l'id del prodotto da acquistare. Il codice che segue mostra questo punto.

private async void InAppPurchaseButton_Click(object sender, RoutedEventArgs e)
{
    try
    {
        var result = await CurrentAppSimulator.RequestProductPurchaseAsync("Product001");
        switch (result.Status)
        {
            case ProductPurchaseStatus.AlreadyPurchased:
                InAppPurchaseResultMessage.Text = "Prodotto già acquistato";
                break;
            case ProductPurchaseStatus.NotFulfilled:
                InAppPurchaseResultMessage.Text = "Acquisto non completato";
                break;
            case ProductPurchaseStatus.NotPurchased:
                InAppPurchaseResultMessage.Text = "Il prodotto non è stato acquistato.";
                break;
            case ProductPurchaseStatus.Succeeded:
                InAppPurchaseResultMessage.Text = "L'acquisto è andato a buon fine.";
                break;
        }
        await this.DisplayProductLicenseInfo();
    }
    catch (Exception ex)
    {
        InAppPurchaseErrorMessage.Text = String.Format("Impossibile acquistare il prodotto: {0}", ex.Message);
        InAppPurchaseErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }
}

Il metodo RequestProductPurchaseAsync restituisce un oggetto di tipo PurchaseResults che contiene le seguenti proprietà:

Proprietà Descrizione
OfferId rappresenta l'identificativo dell'oggetto acquistato
ReceiptXml rappresenta la ricevuta in formato XML relativo all'acquisto appena effettuato
Status un enum di tipo ProductPurchaseStatus che indica l'esito dell'operazione
TransactionId l'identificativo della transazione

È importante sottolineare come l'overload del metodo RequestProductPurchaseAsync che accettava come parametro, oltre all'identificativo del prodotto da acquistare, anche un valore booleano per indicare se si voleva ricevere indietro anche la ricevuta dell'acquisto, con il passaggio a Windows 8.1 è fortemente deprecato. In un'applicazione Windows 8.1, la ricevuta dell'acquisto è infatti incapsulata nella proprietà ReceiptXml dell'oggetto PurchaseResult.

Adesso modifichiamo gli altri metodi per mostrare le nuove informazioni .

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    await this.LoadCustomSimulator();
    await this.DisplayListingInformation();
    await this.DisplayLicenseInfo();
    await this.DisplayProductListingInfo();
    await this.DisplayProductLicenseInfo();
    CurrentAppSimulator.LicenseInformation.LicenseChanged += LicenseInformation_LicenseChanged;
}
private async Task LoadCustomSimulator()
{
    StorageFolder proxyDataFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("trial-configs");
    StorageFile proxyFile = await proxyDataFolder.GetFileAsync("in-app-purchase.xml");
    await CurrentAppSimulator.ReloadSimulatorAsync(proxyFile);
}

A questo punto non ci resta che modificare il codice XAML della pagina come segue:

<StackPanel Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock x:Name="AppId" FontSize="20" Margin="20, 5" />
    <TextBlock x:Name="StoreLink" FontSize="20" Margin="20, 5" />
    <TextBlock x:Name="AppName" FontSize="20" Margin="20, 5" />
    <TextBlock x:Name="LicenseState" FontSize="20" Margin="20, 5"/>
    <TextBlock x:Name="LicenseRemainingDays" FontSize="20" Margin="20, 5" />
    <!--<Button x:Name="BuyButton" Click="BuyButton_Click" Content="Acquista l'app" Margin="20, 5" />-->
    <TextBlock x:Name="PurchaseErrorMessage" FontSize="20" Margin="20, 5" Visibility="Collapsed" />
    <TextBlock x:Name="InAppPurchaseFeatureName" FontSize="20" Margin="20, 5" />
    <TextBlock x:Name="InAppPurchaseFeatureLicenseStatus" FontSize="20" Margin="20, 5" />
    <TextBlock x:Name="InAppPurchaseFeaturePrice" FontSize="20" Margin="20, 5"  />
    <Button x:Name="InAppPurchaseButton" Click="InAppPurchaseButton_Click" Content="Acquista il prodotto" Margin="20, 5" />
    <TextBlock x:Name="InAppPurchaseResultMessage" FontSize="20" Margin="20, 5" />
    <TextBlock x:Name="InAppPurchaseErrorMessage" FontSize="20" Margin="20, 5" Visibility="Collapsed" />
</StackPanel>

Se eseguite l'applicazione, vedrete le nuove informazioni relative al prodotto mostrate a schermo. Come si vede nella prossima immagine, lo stato della licenza relativa alla nuova feature sarà inattivo fino a quando non verrà completato l'in-app purchase.

L'acquisto di beni consumabili

A partire da Windows 8.1, esiste anche la possibilità di acquistare prodotti cosiddetti "consumabili", vale a dire oggetti che possono essere acquistati e quindi "consumati" (o spesi) all'interno dell'app per abilitare determinate funzionalità o fruire di contenuti extra, per poi essere nuovamente acquistati.

Ecco come appare la definizione XML della licenza per un prodotto consumabile: come si può notare, l'attributo ProductType dell'elemento Product è impostato su Consumable:

<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
  <ListingInformation>
    <App>
      <AppId>01234567-1234-1234-1234-0123456789AB</AppId>
      <LinkUri>http://apps.microsoft.com/webpdp/app/01234567-1234-1234-1234-0123456789AB</LinkUri>
      <CurrentMarket>en-US</CurrentMarket>
      <AgeRating>3</AgeRating>
      <MarketData xml:lang="en-us">
        <Name>Consumable Sample</Name>
        <Description>Consumable in-app purchase sample</Description>
        <Price>1.00</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </App>
    <Product ProductId="GoldCoins_100" LicenseDuration="0" ProductType="Consumable">
      <MarketData xml:lang="en-us">
        <Name>100 gold coins</Name>
        <Price>1.00</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
  </ListingInformation>
  <LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
      <ExpirationDate>2014-06-19T09:00:00.00Z</ExpirationDate>
    </App>
  </LicenseInformation>
</CurrentApp>

Per acquistare beni consumabili, il metodo da chiamare è lo stesso usato per acquistare prodotti durevoli, ossia RequestProductPurchaseAsync. La differenza con i consumabili è che, dopo che un acquisto è stato concluso, un utente non può acquistare nuovamente lo stesso prodotto fino a quando l'app ha notificato al Windows Store che il precedente acquisto è stato completato con successo (fulfilled). Il prossimo estratto mostra un esempio:

private Int32 _goldCoins = 0;
private Dictionary<string, List<Guid>> _grantedConsumableTransactionIds = new Dictionary<string, List<Guid>>();
private async void Purchase100GoldCoins_Click(object sender, RoutedEventArgs e)
{
    try
    {
        PurchaseResults purchaseResults = await CurrentAppSimulator.RequestProductPurchaseAsync("GoldCoins_100");
        switch (purchaseResults.Status)
        {
            case ProductPurchaseStatus.Succeeded:
                this.GrantFeatureLocally("GoldCoins_100", purchaseResults.TransactionId);
                await this.FulfillProduct("GoldCoins_100", purchaseResults.TransactionId);
                break;
            case ProductPurchaseStatus.NotFulfilled:
                if (!IsLocallyFulfilled("GoldCoins_100", purchaseResults.TransactionId))
                    this.GrantFeatureLocally("GoldCoins_100", purchaseResults.TransactionId);
                await this.GetUnfulfilledConsumables();
                break;
            case ProductPurchaseStatus.NotPurchased:
                InAppPurchaseResultMessage.Text += "Operazione annullata\n";
                break;
        }
    }
    catch (Exception ex)
    {
        InAppPurchaseErrorMessage.Text = String.Format("Errore: {0}", ex.Message);
        InAppPurchaseErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }
}

Nel caso in cui l'acquisto sia andato a buon fine, il codice per prima cosa abilita il consumo di quel particolare prodotto all'interno dell'applicazione, in modo che l'utente possa cominciare subito a fruirne, quindi notifica allo store il successo dell'operazione tramite il metodo ReportConsumableFulfillmentAsync:

private async Task FulfillProduct(string productId, Guid transactionId)
{
     try
     {
         FulfillmentResult result = await CurrentAppSimulator.ReportConsumableFulfillmentAsync(productId, transactionId);
         switch (result)
         {
             case FulfillmentResult.Succeeded:
                 InAppPurchaseResultMessage.Text += "Acquisto confermato\n";
                 break;
             case FulfillmentResult.NothingToFulfill:
                 InAppPurchaseResultMessage.Text += "Nessun acquisto da confermare\n";
                 break;
             case FulfillmentResult.PurchasePending:
                 InAppPurchaseResultMessage.Text += "In attesa di conferma\n";
                 break;
             case FulfillmentResult.PurchaseReverted:
                 InAppPurchaseResultMessage.Text += "Acquisto revocato\n";
                 break;
             case FulfillmentResult.ServerError:
                 InAppPurchaseResultMessage.Text += "Server error\n";
                 break;
         }
     }
     catch (Exception)
     {
         // errore
     }
}

Nel caso in cui, invece, l'acquisto non risulti confermato, il codice si accerta comunque che l'utente possa già cominciare a fruire del prodotto, dopodiché chiama il metodo GetUnfulfilledConsumables per recuperare l'elenco degli acquisti non confermati (unfulfilled) mediante una chiamata all'API GetUnfulfilledConsumableAsync e tentare così di completare l'operazione:

private async Task GetUnfulfilledConsumables()
{
    try
    {
        IReadOnlyList products = await CurrentAppSimulator.GetUnfulfilledConsumablesAsync();
        foreach (UnfulfilledConsumable product in products)
        {
            await this.FulfillProduct(product.ProductId, product.TransactionId);
        }
    }
    catch (Exception)
    {
        // errore
    }
}

Quello che segue è il codice C# completo per iniziare a sperimentare con l'acquisto di prodotti consumabili. Per un esempio più articolato e complesso, si consiglia di scaricare il sample ufficiale del Windows 8.1 SDK (di cui il codice qui presentato costituisce una versione semplificata per finalità illustrative) a questo indirizzo.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    await this.LoadCustomSimulator();
    await this.DisplayProductListingInfo();
}
private Int32 _goldCoins = 0;
private Dictionary<string, List<Guid>> _grantedConsumableTransactionIds = new Dictionary<string, List<Guid>>();
private async void Purchase100GoldCoins_Click(object sender, RoutedEventArgs e)
{
    try
    {
        PurchaseResults purchaseResults = await CurrentAppSimulator.RequestProductPurchaseAsync("GoldCoins_100");
        switch (purchaseResults.Status)
        {
            case ProductPurchaseStatus.Succeeded:
                this.GrantFeatureLocally("GoldCoins_100", purchaseResults.TransactionId);
                await this.FulfillProduct("GoldCoins_100", purchaseResults.TransactionId);
                break;
            case ProductPurchaseStatus.NotFulfilled:
                if (!IsLocallyFulfilled("GoldCoins_100", purchaseResults.TransactionId))
                    this.GrantFeatureLocally("GoldCoins_100", purchaseResults.TransactionId);
                await this.GetUnfulfilledConsumables();
                break;
            case ProductPurchaseStatus.NotPurchased:
                InAppPurchaseResultMessage.Text += "Operazione annullata\n";
                break;
        }
    }
    catch (Exception ex)
    {
        InAppPurchaseErrorMessage.Text = String.Format("Errore: {0}", ex.Message);
        InAppPurchaseErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
    }
}
private async Task FulfillProduct(string productId, Guid transactionId)
{
    try
    {
        FulfillmentResult result = await CurrentAppSimulator.ReportConsumableFulfillmentAsync(productId, transactionId);
        switch (result)
        {
            case FulfillmentResult.Succeeded:
                InAppPurchaseResultMessage.Text += "Acquisto confermato\n";
                break;
            case FulfillmentResult.NothingToFulfill:
                InAppPurchaseResultMessage.Text += "Nessun acquisto da confermare\n";
                break;
            case FulfillmentResult.PurchasePending:
                InAppPurchaseResultMessage.Text += "In attesa di conferma\n";
                break;
            case FulfillmentResult.PurchaseReverted:
                InAppPurchaseResultMessage.Text += "Acquisto revocato\n";
                break;
            case FulfillmentResult.ServerError:
                InAppPurchaseResultMessage.Text += "Server error\n";
                break;
        }
    }
    catch (Exception)
    {
        // errore
    }
}
private void GrantFeatureLocally(string productId, Guid transactionId)
{
    if (!this._grantedConsumableTransactionIds.ContainsKey(productId))
    {
        this._grantedConsumableTransactionIds.Add(productId, new List<Guid>());
    }
    this._grantedConsumableTransactionIds[productId].Add(transactionId);
    this._goldCoins += 100;
    NumberOfGoldCoins.Text = String.Format("Adesso hai {0} monete d'oro", this._goldCoins);
}
private async Task GetUnfulfilledConsumables()
{
    try
    {
        IReadOnlyList<UnfulfilledConsumable> products = await CurrentAppSimulator.GetUnfulfilledConsumablesAsync();
        foreach (UnfulfilledConsumable product in products)
        {
            this.GrantFeatureLocally(product.ProductId, product.TransactionId);
            await this.FulfillProduct(product.ProductId, product.TransactionId);
        }
    }
    catch (Exception)
    {
        // errore
    }
}
private Boolean IsLocallyFulfilled(string productId, Guid transactionId)
{
    return this._grantedConsumableTransactionIds.ContainsKey(productId) && this._grantedConsumableTransactionIds[productId].Contains(transactionId);
}
private async Task DisplayProductListingInfo()
{
    try
    {
        NumberOfGoldCoins.Text = String.Format("Al momento hai {0} monete d'oro", this._goldCoins);
        ListingInformation listingInfo = await CurrentAppSimulator.LoadListingInformationAsync();
        var productListing = listingInfo.ProductListings["GoldCoins_100"];
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            InAppPurchaseFeatureName.Text = String.Format("Nome del prodotto: {0}", productListing.Name);
            InAppPurchaseFeaturePrice.Text = String.Format("Prezzo: {0}", productListing.FormattedPrice);
        });
    }
    catch (Exception ex)
    {
        Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            InAppPurchaseErrorMessage.Text = String.Format("Impossibile recuperare le informazioni: {0}", ex.Message);
            InAppPurchaseErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible;
        });
    }
}
private async Task LoadCustomSimulator()
{
    StorageFolder proxyDataFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("trial-configs");
    StorageFile proxyFile = await proxyDataFolder.GetFileAsync("consumable-purchase.xml");
    await CurrentAppSimulator.ReloadSimulatorAsync(proxyFile);
}

Per testare questo codice potete usare la seguente definizione XAML come riferimento per la pagina principale della vostra applicazione:

<Page
    x:Class="Demo.Html.it.TrialSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Demo.Html.it.TrialSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <TextBlock x:Name="NumberOfGoldCoins" FontSize="20" Margin="20, 5" />
            <TextBlock x:Name="InAppPurchaseFeatureName" FontSize="20" Margin="20, 5" />
            <TextBlock x:Name="InAppPurchaseFeaturePrice" FontSize="20" Margin="20, 5"  />
            <Button x:Name="InAppPurchaseButton" Click="Purchase100GoldCoins_Click" Content="Acquista 100 monete d'oro" Margin="20, 5" />
            <TextBlock x:Name="InAppPurchaseResultMessage" FontSize="20" Margin="20, 5" />
            <TextBlock x:Name="InAppPurchaseErrorMessage" FontSize="20" Margin="20, 5" Visibility="Collapsed" />
        </StackPanel>
    </Grid>
</Page>

Se adesso eseguite l'app, vedrete aumentare le vostre scorte di monete d'oro ogni volta che premete sul pulsante:

Recuperare la ricevuta dell'acquisto

A volte può essere necessario controllare la ricevuta di una transazione, ad esempio per validare l'acquisto della licenza da parte dell'utente (per un esempio completo si rinvia a MSDN). Il namespace Windows.ApplicationModel.Store prevede due strade per recuperare questa ricevuta.

La prima opzione consiste nel richiedere la ricevuta nel momento stesso in cui viene perfezionato l'acquisto della licenza full dell'app ovvero di un prodotto tramite in-app purchase. Nel primo caso, occorre passare true come parametro al metodo RequestAppPurchaseAsync, mentre nel caso di in-app purchase la ricevuta è automaticamente incapsulata all'intero dell'oggetto PurchaseResults restituito dal metodo GetProductReceiptAsync.

La seconda strada è quella di recuperare la ricevuta relativa all'app o a un prodotto specifico chiamando, rispettivamente, i metodi GetAppReceiptAsync e GetProductReceiptAsync. Entrambi questi metodi restituiscono una stringa XML che include tutte le informazioni relative alla transazione, incluso il numero di ricevuta, l'id dell'app, la data di acquisto e il tipo di licenza acquistata. Il prossimo listato mostra un esempio:

<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>***TRedhKq6zrzCfT8qfh3C1w==</SignatureValue>
  </Signature>
</Receipt>

Da notare come l'elemento Receipt includa, fra i suoi attributi, l'identificativo della ricevuta, l'impronta del certificato utilizzato per firmarla (si veda in argomento l'articolo di questa guida dedicato alla sicurezza dei dati [[LINK]]), la data di emissione della ricevuta, il tipo di licenza acquistata, informazioni sugli acquisti mediante in-app purchase, nonché una serie di dati sul tipo di protezione adottato.

Per quanto riguarda l'acquisto di prodotti, vale la pena osservare come l'attributo ProductType dell'elemento ProductReceipt indichi il tipo di risorsa acquistata. Questa proprietà può assumere, come si è visto, due valori: Consumable, quando il prodotto acquistato può essere acquistato, consumato e quindi acquistato nuovamente; oppure Durable, nel caso opposto.

Ti consigliamo anche