Phone 8 Wallet IV : Wallet and NFC

Tags: WP8, Windows Phone 8, Wallet, WinRT, NFC

This is the last part of a series about the Windows Phone 8 Wallet.

The menu :

Phone 8 Wallet I : Deals

Phone 8 Wallet II : Membership and Transactions

Phone 8 Wallet III : WalletAgents

Phone 8 Wallet IV : Wallet and NFC

 

For this last part, we will mix Wallet with NFC. In fact, there is nothing new about Wallet here, it is just an example of how to use it with NFC

I made a sample (link at the end) about an NFC-enabled wallet application. Basically it is a membership application where you can add credits through NFC tags. You can also create your own tags.

 

Don’t forget to add Wallet and NFC (Proximity) capabilities to the application :

Creating the NFC tag

For that, you need some NDEF formatted tags. NDEF is a standard format for NFC messages. You can have some info here and here.

You can have different kind of NDEF messages for different purposes. URL ones, Text ones, etc… The one I will use is the LaunchApp one. So when the device will read the tag, it will launch our application. If the application is not on the device, it will point to the application on the Marketplace so you can download it.

You can have parameters in the LaunchApp. The parameter will be our serialized coupon. If you want to know more about LaunchApp, read this article from Phone Dev MVP Rudy Huyn (In French only).

Here is my coupon class :

using System;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace WalletNFC
{
    public class CouponNFC
    {
        private const string _key = "thisisakey123456";
        private const string _iv = "thisisiv12345678";

        public int ID { get; set; }

        public int Credits { get; set; }

        public DateTime? ValidityDate { get; set; }

        public override string ToString()
        {
            // Serialize and encrypt data
            var s = string.Format("{0}|{1}|{2}", ID, Credits, 
                ValidityDate.HasValue ? ValidityDate.Value.ToString(new CultureInfo("fr-be")) : string.Empty);

            return EncryptString(s);
        }

        public CouponNFC(string s = null)
        {
            if (s == null)
                return;

            var decrypted = DecryptString(s);

            var data = decrypted.Split(new char[] {'|'});

            ID = int.Parse(data[0]);

            Credits = int.Parse(data[1]);

            if (!string.IsNullOrWhiteSpace(data[2]))
            {
                ValidityDate = DateTime.Parse(data[2], new CultureInfo("fr-be"));
            }

        }


        static private string EncryptString(string plainText)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");

            byte[] encrypted;
            // Create an AesManaged object
            // with the specified key and IV.
            using (var aesAlg = new AesManaged())
            {
                aesAlg.Key = Encoding.UTF8.GetBytes(_key);
                aesAlg.IV = Encoding.UTF8.GetBytes(_iv);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (var msEncrypt = new MemoryStream())
                {
                    using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (var swEncrypt = new StreamWriter(csEncrypt))
                        {

                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }


            return Convert.ToBase64String(encrypted, 0, encrypted.Length);

        }

        static private string DecryptString(string cipherText)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");


            // Declare the string used to hold
            // the decrypted text.
            string plaintext;

            // Create an AesManaged object
            // with the specified key and IV.
            using (var aesAlg = new AesManaged())
            {
                aesAlg.Key = Encoding.UTF8.GetBytes(_key);
                aesAlg.IV = Encoding.UTF8.GetBytes(_iv);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (var msDecrypt = new MemoryStream(Convert.FromBase64String(cipherText)))
                {
                    using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (var srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }

            return plaintext;

        }
    }

}

 

I override the ToString() method so it serialize the coupon into an encrypted string. I made also a constructor that accept an encrypted string so you can deserialize a coupon. On a side note, it would have probably be cleaner implementing a static method like CouponNFC.FromString(string s) but that is not the point of this article.

I used basic encrypt/decrypt method found on MSDN for string encryption.

 

Now for the NFC NDEF message part itself. A LaunchApp message must have this format : <data>/tPlatform/ApplicationID. So for my application I will have something like : <encrypteddata>/tWindowsPhone/t{4d8d8f2b-f6de-4c37-9f89-8fa81d2a3604} (Which is the ID of my application, as you can see in the manifest)

To write the tag, here is the code :

private void WriteNFC(string data)
{
  // Write an LaunchApp message
  var launchAppMessage = string.Format("{0}\tWindowsPhone\t{{4d8d8f2b-f6de-4c37-9f89-8fa81d2a3604}}", data);

  var dataWriter = new DataWriter() { UnicodeEncoding = UnicodeEncoding.Utf16LE };
  dataWriter.WriteString(launchAppMessage);
  ProximityDevice.GetDefault().PublishBinaryMessage("LaunchApp:WriteTag", dataWriter.DetachBuffer(), (s, m) =>
       {
           ProximityDevice.GetDefault().StopPublishingMessage(m);
           Deployment.Current.Dispatcher.BeginInvoke(() => MessageBox.Show("The message is written"));
         });

}

Reading the tag

So now the message is written to the tag. But what will happen when the device scan a tag ?

You’ll get this screen (If you don’t have the application installed, you will be asked if you want to download it from the marketplace) :

If you click “open”, it will launch your app. How do you know it was launched by NFC ? And where are the data embedded into the message?

Well, you have to check the NavigationContext of your main page. In the context, you’ll have a QueryString object containing a ‘ms_nfp_launchargs’ key. This is the data key.

 

An example :

 protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 
{
     base.OnNavigatedTo(e);

     // if it is launched from an NFC Tag
     if (NavigationContext.QueryString.ContainsKey("ms_nfp_launchargs"))
     {
         try
         {
             // Deserialize the coupon
             var coupon = new CouponNFC(NavigationContext.QueryString["ms_nfp_launchargs"]);

             // check if valid
             if ((coupon.ValidityDate.HasValue) && (coupon.ValidityDate.Value.Date < DateTime.Now.Date))
             {
                 MessageBox.Show("Coupon expired");
                 return;
             }

             if (MessageBox.Show(string.Format("Do you want to add {0} credits ?", coupon.Credits), "", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
             {
                 AddCredits(coupon);
             }
         }
         catch (Exception)
         {
             MessageBox.Show("Error decoding the coupon");
         }
     }
 }

Putting all that logic into the OnNavigatedTo is not good practice, but it is just to show how to get the data passed on. There is a better implementation in my sample app.

I check if there is a “ms_nfp_launchargs” key in the NavigationContext Query. If there is, I take it and a construct a coupon from it.

Now that I have the coupon, I am checking if the coupon is not expired. If it is not, I add the credits.

Wallet part

Now we have the coupon, we can use it. It is same thing as I showed in part II, about membership.

To be sure the coupon is not reused, I save the transactions. The transaction ID is the coupon ID. So before adding it, I check if there is a transaction ID with the coupon ID. If there is not, I add the credits to the membership card.

It is just a basic implementation, you could rewrite the tag to invalidate it, or check a web service,…

private async void AddCredits(CouponNFC coupon)
{
 var items = await Wallet.GetItemsAsync();

 //get wallet items and check if one already exist fro this app
 var walletitem = items.FirstOrDefault(d => d is WalletTransactionItem) as WalletTransactionItem;
 
           
 // if coupon ID already in history, it was already used
 if (walletitem.TransactionHistory.ContainsKey(coupon.ID.ToString()))
 {
    MessageBox.Show("Coupon already used");
    return;
 }

 // change balance, add transaction history and save 
 var balance = int.Parse(walletitem.DisplayBalance);
 balance += coupon.Credits;
 walletitem.DisplayBalance = balance.ToString();

 walletitem.TransactionHistory.Add(coupon.ID.ToString(), new WalletTransaction 
                { 
                   Description = "NFC Coupon",
                   DisplayAmount = coupon.Credits.ToString(),
                   TransactionDate = DateTime.Now 
                });
 
 await walletitem.SaveAsync();
 MessageBox.Show("Credits added");
}

Conclusion

It’s a wrap for this little introduction to Wallet. The possibilities of Wallet and NFC are limitless, I just barely scratched the surface with my articles. I can’t wait to see the applications that will be created.

The application sample is here

You have two screens, one to add a coupon (if you scan one) and one to create one :

 

Important : You need NDEF compatible and formatted tags (WP8 cannot format tags) ! Most companies selling tags have an option to have it NDEF formatted (Or filled with an NDEF message, which is the same as it must be formatted to do that). Just don’t ask for locked ones, as thoses are read only !

Personally I bought the Ultimate Started Pack on RapidNFC. I asked them to be encoded with URL (so they are NDEF formatted) and non locked.

Comments powered by Disqus