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 definizione XML custom che include un nuovo prodotto acquistabile (per semplicità, la cui durata è indefinita). Questa definizione è contenuta in un nuovo file XML, denominato in-app-purchase.xml, che verrà quindi passato al metodo ReloadAppSimulatorAsync.

<?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>In-app purchase</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 occorrebbe 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.

function displayProductListingInfo() {
    currentApp.loadListingInformationAsync().done(
        function (listingInfo) {
            var productListing = listingInfo.productListings.lookup("Product001");
            featureName.innerHTML = "
Nome del prodotto: " + productListing.name + "
";
            featurePrice.innerHTML = "
Prezzo: " + productListing.formattedPrice + "
";
        },
        function (err) {
            featurePurchaseError.innerHTML = "
Impossibile recuperare le informazioni: " + err + "
";
        }
    );
}

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.

function displayProductLicenseInfo() {
    try {
        var productLicense = currentApp.licenseInformation.productLicenses.lookup("Product001");
        if (!productLicense.isActive)
            featureLicenseStatus.innerHTML = "<p>Prodotto non attivo. Non puoi usare il prodotto.</p>";
        else {
            featureLicenseStatus.innerHTML = "<p>Prodotto attivo. Adesso puoi usarlo.</p>";
        }
    }
    catch (ex) {
        featurePurchaseError.innerHTML = "<p>Impossibile recuperare informazioni sulla licenza</p>";
    }
}

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.

function buyProduct_Click(args) {
    currentApp.requestProductPurchaseAsync("Product001").done(
        function (result) {
            switch (result.status) {
                case Windows.ApplicationModel.Store.ProductPurchaseStatus.alreadyPurchased:
                    featurePurchaseMessage.innerHTML = "Prodotto già acquistato.";
                    break;
                case Windows.ApplicationModel.Store.ProductPurchaseStatus.notFulfilled:
                    featurePurchaseMessage.innerHTML = "Acquisto non completato.";
                    break;
                case Windows.ApplicationModel.Store.ProductPurchaseStatus.notPurchased:
                    featurePurchaseMessage.innerHTML = "Il prodotto non è stato acquistato."
                    break;
                case Windows.ApplicationModel.Store.ProductPurchaseStatus.succeeded:
                    featurePurchaseMessage.innerHTML = "L'acquisto è andato a buon fine.";
                    break;
            }
        },
        function (err) {
            featurePurchaseMessage.innerHTML = "<p>Impossibile acquistare il prodotto</p>.";
        });
}

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 (le modifiche sono in grassetto).

app.onloaded = function () {
    //buyAppButton.addEventListener("click", buyButton_click, false);
    buyProductButton.addEventListener("click", buyProduct_Click, false);
    loadCustomSimulator();
};
// ...
function reloadLicense() {
    displayLicenseInfo();
    displayListingInfo();
    displayProductListingInfo();
    displayProductLicenseInfo();
}
// ...
function loadCustomSimulator() {
    Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("trial-configs")
        .then(function (folder) {
            return folder.getFileAsync("in-app-purchase.xml");
        }).then(function (file) {
            currentApp.licenseInformation.onlicensechanged = reloadLicense;
            currentApp.reloadSimulatorAsync(file);
        }).done(
            null,
            function (err) {
                // gestire errore
            });
}

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

<body>
    <div id="appId"></div>
    <div id="storeLink"></div>
    <div id="appName"></div>
    <div id="licenseState"></div>
    <div id="licenseRemainingDays"></div>
    <!--<button id="buyAppButton">Acquista l'app</button>-->
    <div id="purchaseMessage"></div>
    <div id="featureName"></div>
    <div id="featureLicenseStatus"></div>
    <div id="featurePrice"></div>
    <button id="buyProductButton">Acquista il prodotto</button>
    <div id="featurePurchaseMessage"></div>
</body>

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:

var currentApp = Windows.ApplicationModel.Store.CurrentAppSimulator;
var goldCoins = 0;
var grantedIds = {
    "GoldCoins_100": []
};
function purchase100GoldCoins_click(args) {
    currentApp.requestProductPurchaseAsync("GoldCoins_100")
        .done(function (purchaseResults) {
            if (purchaseResults.status === Windows.ApplicationModel.Store.ProductPurchaseStatus.succeeded) {
                grantFeatureLocally("GoldCoins_100", purchaseResults.transactionId);
                fulfillProduct("GoldCoins_100", purchaseResults.transactionId);
            } else if (purchaseResults.status === Windows.ApplicationModel.Store.ProductPurchaseStatus.notFulfilled) {
                if (!isLocallyFulfilled("GoldCoins_100", purchaseResults.transactionId)) {
                    grantFeatureLocally("GoldCoins_100", purchaseResults.transactionId);
                }
                getUnfulfilledConsumables();
            } else if (purchaseResults.status === Windows.ApplicationModel.Store.ProductPurchaseStatus.notPurchased) {
                featurePurchaseResult.innerHTML = "<p>Operazione annullata.</p>";
            }
        }
    );
}

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:

function fulfillProduct(productId, transactionId) {
    currentApp.reportConsumableFulfillmentAsync(productId, transactionId)
        .done(function (result) {
            switch (result) {
                case Windows.ApplicationModel.Store.FulfillmentResult.succeeded:
                    featurePurchaseResult.innerHTML += "<p>Acquisto confermato.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.nothingToFulfill:
                    featurePurchaseResult.innerHTML += "<p>Nessun acquisto da confermare.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.purchasePending:
                    featurePurchaseResult.innerHTML += "<p>In attesa di conferma.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.purchaseReverted:
                    featurePurchaseResult.innerHTML += "<p>Acquisto revocato.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.serverError:
                    featurePurchaseResult.innerHTML += "<p>Server error</p>";
                    break;
            }
        },
        function (error) {
            featurePurchaseResult.innerHTML += "<p>Impossibile completare l'operazione</p>";
        });
}

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:

function getUnfulfilledConsumables() {
    currentApp.getUnfulfilledConsumablesAsync()
        .done(function (unfulfilledList) {
            unfulfilledList.forEach(function (product) {
                if (!isLocallyFulfilled(product.productId, product.transactionId)){
                    grantFeatureLocally(product.productId, product.transactionId);
                }
                fulfillProduct(product.productId, product.transactionId);
            });
        },
        function (error) {
            // errore
        });
}

Quello che segue è il codice JavaScript 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.

app.onloaded = function () {
    btnBuyConsumable.addEventListener("click", purchase100GoldCoins_click);
    loadCustomSimulator();
}
var currentApp = Windows.ApplicationModel.Store.CurrentAppSimulator;
var goldCoins = 0;
var grantedIds = {
    "GoldCoins_100": []
};
function purchase100GoldCoins_click(args) {
    currentApp.requestProductPurchaseAsync("GoldCoins_100")
        .done(function (purchaseResults) {
            if (purchaseResults.status === Windows.ApplicationModel.Store.ProductPurchaseStatus.succeeded) {
                grantFeatureLocally("GoldCoins_100", purchaseResults.transactionId);
                fulfillProduct("GoldCoins_100", purchaseResults.transactionId);
            } else if (purchaseResults.status === Windows.ApplicationModel.Store.ProductPurchaseStatus.notFulfilled) {
                if (!isLocallyFulfilled("GoldCoins_100", purchaseResults.transactionId)) {
                    grantFeatureLocally("GoldCoins_100", purchaseResults.transactionId);
                }
                getUnfulfilledConsumables();
            } else if (purchaseResults.status === Windows.ApplicationModel.Store.ProductPurchaseStatus.notPurchased) {
                featurePurchaseResult.innerHTML = "<p>Operazione annullata.</p>";
            }
        }
    );
}
function fulfillProduct(productId, transactionId) {
    currentApp.reportConsumableFulfillmentAsync(productId, transactionId)
        .done(function (result) {
            switch (result) {
                case Windows.ApplicationModel.Store.FulfillmentResult.succeeded:
                    featurePurchaseResult.innerHTML = "<p>Acquisto confermato.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.nothingToFulfill:
                    featurePurchaseResult.innerHTML = "<p>Nessun acquisto da confermare.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.purchasePending:
                    featurePurchaseResult.innerHTML = "<p>In attesa di conferma.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.purchaseReverted:
                    featurePurchaseResult.innerHTML = "<p>Acquisto revocato.</p>";
                    break;
                case Windows.ApplicationModel.Store.FulfillmentResult.serverError:
                    featurePurchaseResult.innerHTML = "<p>Server error</p>";
                    break;
            }
        },
        function (error) {
            featurePurchaseResult.innerHTML = "<p>Impossibile completare l'operazione</p>";
        });
}
function getUnfulfilledConsumables() {
    currentApp.getUnfulfilledConsumablesAsync()
        .done(function (unfulfilledList) {
            unfulfilledList.forEach(function (product) {
                if (!isLocallyFulfilled(product.productId, product.transactionId)){
                    grantFeatureLocally(product.productId, product.transactionId);
                }
                fulfillProduct(product.productId, product.transactionId);
            });
        },
        function (error) {
            // errore
        });
}
function isLocallyFulfilled(productId, transactionId) {
    for (var i in grantedIds[productId]) {
        if (grantedIds[productId][i] === transactionId) {
            return true;
        }
    }
    return false;
}
function grantFeatureLocally(productId, transactionId) {
    var nextIndex = grantedIds[productId].length;
    grantedIds[productId][nextIndex] = transactionId;
    goldCoins += 100;
    NumberOfGoldCoins.innerHTML = "<p>Adesso hai " + goldCoins + " monete d'oro</p>";
}
function loadCustomSimulator() {
    Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("trial-configs")
        .then(function (folder) {
            return folder.getFileAsync("consumable-purchase.xml");
        }).then(function (file) {
            currentApp.licenseInformation.onlicensechanged = reloadLicense;
            currentApp.reloadSimulatorAsync(file);
        }).done(
            null,
            function (err) {
                // gestire errore
            });
}
function displayProductListingInfo() {
    NumberOfGoldCoins.innerHTML = "<p>Al momento hai " + goldCoins + " monete d'oro";
    currentApp.loadListingInformationAsync().done(
        function (listingInfo) {
            var productListing = listingInfo.productListings.lookup("GoldCoins_100");
            featureName.innerHTML = "<p>Nome del prodotto: " + productListing.name + "</p>";
            featurePrice.innerHTML = "<p>Prezzo: " + productListing.formattedPrice + "</p>";
        },
        function (err) {
            featureName.innerHTML = "<p>Impossibile recuperare le informazioni: " + err + "</p>";
        }
    );
}
function reloadLicense() {
    displayProductListingInfo();
}

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo.Html.it.ConsumableProducts.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Demo.Html.it.ConsumableProducts.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div id="NumberOfGoldCoins"></div>
    <div id="featureName"></div>
    <div id="featurePrice"></div>
    <button id="btnBuyConsumable">Acquista 100 monete d'oro</button>
    <div id="featurePurchaseResult"></div>
</body>
</html>

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