Partilhar via


Testing in-app purchases/Microtransactions for C#/XAML Store apps

Background

Many people like to write code just for the fun of it, but i can guarantee you that getting paid is much more rewarding!  Windows Phone's first release cycle left you with two options for making money...advertising or paid apps.  Most people went the paid app with an ad-supported trial initially but found out quickly that your "trial" free app never showed up in the free section so the majority of the population completely skipped your app.  The smart developers quickly released a second version of their app that was a separate 'free' version and updated both apps when they released updates.  Fast forward to now, unless you were the only competitor for your app type and had a laser focused target audience that you knew would pay for an app the free/ad supported version came out on top monetarily.  For me personally I was earning anywhere from 10x to 20x as much on my free app with ads opposed to my paid app.  Lessons learned?  MAKE YOUR APP FREE!

That said Windows Store 8 (and phone 8) brings in the MUCH MUCH needed option of micro-transactions!  Thus begins the new era of freemium apps and though there are a million different opinions on how to properly structure your app to best to capitalize on this we'll stick to the implementation here.  I will say that any app with freemium features should reward those who want to pay but also not punish those who don't.  As a quick example - my primary app (for now) is your a-typical gun/shotgun app, I have a handful of guns that are locked and can be unlocked 2 different ways.  Way number 1 is micro-transactions (obviously).  You can unlock a set of guns for the minimum amount of 1.49 US, or buy a certain amount of in game currency (bullets) to unlock the same "value" of guns except pick and choose which to unlock.  Any purchase automatically disables the Ad forever as well (it's estimated each user is worth 3-5 cents over the lifetime of an ad-supported app) and I'm happy to do so given the value proposition.  Option number 2 is to spend in game currency (bullets) which you earn for every weapon discharge and for doing in game "missions".  There is nothing that can't be unlocked or done simply by using the app but if someone just wants to unlock a large set of features right away and get rid of the Ad they can.  I don't think this will be too viable initially but as I grow the app adding more unlock-able features it becomes more attractive for the target audience.

How To setup and test

Ok!  I'm going to skim over creating your micro-transaction stuff initially since there is decent documentation on it thus far but it wasn't 100% clear upon reading it what was needed.  I'm assuming you at least have the includes (Windows.ApplicationMdoel.Store).

Before we code you need to define a few things...and this may seem trivial but it's more important than all of the below.  See, once you define your initial unlockables you must ALWAYS support them.  If you change your price, or what it unlocks that's fine but you can't change the keys you initially assign and must always support them so choose wisely.  for my example lets pick 2 things, one is 1000 in game currency called "1000Bullets" and another is a weapon pack "weaponPack1".  Another requirement is to have a windowsStoreProxy XML file that will be used by the current app simulator to test your app.  Now if you don't feel like creating one initially, your app WILL create a basic one for you by default if you add said code below.  Of course when you hit the code block the solution will break into the debugger though it doesn't crash the app so feel free to continue.  After doing so once, the XML file is created and you can edit it to your hearts content  I would recommend this path as one mistake in the file and it becomes incredibly hard to debug what's not working correctly (yes, this happened to me, hence why I'm recommending said path).

First in your code behind page (mainpage.xaml.cs) declare a global variable:

public LicenseInformation licenseInfo;

Next, you'll need to populate said variable and do some form of featureChecking.  This is where you need to be a bit creative as there are two ways to populate your licenseInfo depending on if your app is in the store or not.  Since you are testing new unlockables anyways you need to use the non release code method which is currentAppSimulator.  Usually when I complete a feature set and get ready to push my code live I get so gosh darned FIRED UP that I forget to change my flags properly so I try and build my code to support my shortcomings.  Thus I detect whether I'm in debug mode, thus below:

 private void featureCheck()
 {
 try
 {
 if (System.Diagnostics.Debugger.IsAttached)
 {
 licenseInfo = currentAppSimulator.LicenseInformation;
 }
 else
 {
 licenseInfo = currentApp.LicenseInformation;
 }
 if (licenseInfo.productLicenses["1000Bullets"].isActive)
 {
 //unlock feature
 }
 (licenseInfo.productLicenses["weaponPack1"].isActive)
 {
 //unlock feature
 }
 catch
 {
 //keeps your code from crashin', do some error reporting
 }
 }

Time to break it down.  So I'm checking if this is going down in the emulator, if so use the currentAppSimulator which covers my over-excited behind in my pre-release exuberance.  I also include everything in try/catch blocks so the app don't crash if i don't have the proxy file setup.  This is important because EVERYTIME your app redeploys fully (change architecture, reboot, etc) your xml file gets blown away.  This way your app can still run after you've verified everything works, or it's a reminder to go update your proxy XML file.  Finally you check if the features are unlocked, if they are do something otherwise they should remain locked.  Bam.

Next up lets edit that XML file  It looks like the below by default:

 <?xml version="1.0" encoding="utf-16" ?> <CurrentApp>
 <ListingInformation>
 <App>
 <AppId>00000000-0000-0000-0000-000000000000</AppId>
 <LinkUri>https://apps.microsoft.com/webpdp/app/00000000-0000-0000-0000-000000000000</LinkUri>
 <CurrentMarket>en-US</CurrentMarket>
 <AgeRating>3</AgeRating>
 <MarketData xml:lang="en-us">
 <Name>AppName</Name>
 <Description>AppDescription</Description>
 <Price>1.00</Price>
 <CurrencySymbol>$</CurrencySymbol>
 <CurrencyCode>USD</CurrencyCode>
 </MarketData>
 </App>
 <Product ProductId="1" LicenseDuration="0">
 <MarketData xml:lang="en-us">
 <Name>Product1Name</Name>
 <Price>1.00</Price>
 <CurrencySymbol>$</CurrencySymbol>
 <CurrencyCode>USD</CurrencyCode>
 </MarketData>
 </Product>
 </ListingInformation>
 <LicenseInformation>
 <App>
 <IsActive>true</IsActive>
 <IsTrial>true</IsTrial>
 </App>
 <Product ProductId="1"
 <IsActive>true</IsActive>
 </Product>
 </LicenseInformation>
 </CurrentApp>
 

Two VERY IMPORTANT things to note here.  One, your app DOES NOT change this file.  When you are in testing mode and try and purchase things, you get back the return codes but this file is never changed, nor is your app so you control the license.isActive state from here and from here alone.  Again, when you are in testing mode all the commands sent and received are governed by this file and your app will NOT change itso can't test the actual unlocking of a feature except by changing this file and seeing your app react.  I read this somewhere and it didn't sink in initially so I had a tough time figuring out why my app wasn't unlocking the features.  I'm changing TWO area's, first is the product in the listing area which details the price data, etc. the other is in the licenseInformation which determines the isActive return.  I don't bother with anything else.

 <?xml version="1.0" encoding="utf-16" ?> <CurrentApp>
 <ListingInformation>
 <App>
 <AppId>00000000-0000-0000-0000-000000000000</AppId>
 <LinkUri>https://apps.microsoft.com/webpdp/app/00000000-0000-0000-0000-000000000000</LinkUri>
 <CurrentMarket>en-US</CurrentMarket>
 <AgeRating>3</AgeRating>
 <MarketData xml:lang="en-us">
 <Name>AppName</Name>
 <Description>AppDescription</Description>
 <Price>1.00</Price>
 <CurrencySymbol>$</CurrencySymbol>
 <CurrencyCode>USD</CurrencyCode>
 </MarketData>
 </App>
 <Product ProductId="weaponsUnlock1" LicenseDuration="0">
 <MarketData xml:lang="en-us">
 <Name>Unlock 3 Guns</Name>
 <Price>1.49</Price>
 <CurrencySymbol>$</CurrencySymbol>
 <CurrencyCode>USD</CurrencyCode>
 </MarketData>
 </Product>
 <Product ProductId="1000Bullets" LicenseDuration="0">
 <MarketData xml:lang="en-us">
 <Name>1000 Bullet Currency</Name>
 <Price>1.49</Price>
 <CurrencySymbol>$</CurrencySymbol>
 <CurrencyCode>USD</CurrencyCode>
 </MarketData>
 </Product>
 </ListingInformation>
 <LicenseInformation>
 <App>
 <IsActive>true</IsActive>
 <IsTrial>true</IsTrial>
 </App>
 <Product ProductId="1000Bullets">
 <IsActive>true</IsActive>
 </Product>
 <Product ProductId="weaponsUnlock1">
 <IsActive>false</IsActive>
 </Product>
 </LicenseInformation>
 </CurrentApp>

Now go ahead and test out your app.  With the above XML file the licenseInfo["1000Bullets"].isActive in your c# code should return true and you can verify that your code is properly unlocking said feature.  Double Bam.

The only missing piece now is how you handle the purchase of items, but this is in fact the easiest piece!  Lets say you go ahead and create a gridview  and bind your store objects to it.  How do you handle user selection of an item?  Well the best part about this is that the UI piece is handled completely by the store so you only need to make the call and your good to go.  Now remember, when you are in the simulator/local machine when you make the call using currentAppSimulator you get a pop up that lets you return various messages (Ok, network issues, etc) and see how your code handles the response but the license file DOES NOT CHANGE so the feature won't unlock regardless of the message you send back.  Obviously this doesn't happen in the live store but keep that in mind for testing purposes, to simulate the item being purchased you need to edit the XAML file above.  

 //class used for purchasable objects
 public Class PurchaseObject
 {
 string name {get; set;}
 string key {get; set;}
 int cost {get; set;}
 }
 
 private async void gridviewItem_Click_1(object sender, ItemCLickEventArgs e)
 {
 PurchaseObject element = e.ClickedItem as PurchaseObject;
 if(!licenseInfo.ProductLicenses[element.key].IsActive)
 {
 try
 {
 string success;
 if(System.Diagnostics.Debugger.IsAttached)
 {
 success = await CurrentAppSimulator.RequestProductPurchaseAsync(element.key, false);
 }
 else
 {
 success = await CurrentApp.RequestProductPurchaseAsync(element.key, false);
 }
 //do some form of feature checking at this point
 // read the success string if you want to see what
 //happened
 }
 catch
 {
 //network issues or store connection issues
 }
 }

So we create a class real quick that contains the name, price and key of the purchasable object.  The name and cost will obviously be used for displaying in the gridview, but the key is what's really important here.  The key is the same productId you used above for the feature and is the same string you'll use when submitting your app to the store.  The click event checks if the feature is already unlocked and if it is it does nothing (you can add en else clause to notify the user they already own it) but in theory you probably aren't displaying any objects that are already purchased so this is more of an extra safety check.  Next is a try/catch block in case of network issues, store being down, etc.  Then we have the if Debugger attached to determine if your in the simulator,  and finally the store API call.  The call does everything for you UI wise so that's it.  If your in the simulator you get a pop up to send back a message but it doesn't change anything as mentioned above.  Hope this helps some of you out with your end to end solution and if you make a million dollars with my amazing suggestions, give 5 dollars to charity!  Happy Coding!

Comments

  • Anonymous
    August 24, 2015
    Erik, thanks for the suggestions. The Microsoft documentation is lacking.