What’s New in Windows Phone 8 (8 out of 8)–Wallet

November 9, 2012

Windows Phone 8 adds new interesting feature – the Wallet – which helps users to manage their virtual cards (membership, credit, debit), coupons and use them for in-app and real-world transactions.

The documentation states, that Wallet helps users to:

  • Collect coupons, credit cards, memberships, loyalty cards, and more in one place.
  • Manage the payment instruments that they use in the app and music store.
  • Link items in the Wallet to apps on their phone.
  • Make contactless transactions, using Near-Field Communication (NFC), in some markets

Wallet exposes APIs which enable developers to create, read, write, and delete in-Wallet items from application. It also enables update items by using agents and interact with the user through messages and custom fields. The application and agents could connect to application’s server side functionality to provide updates about new items, bill availability and update balance of stored Wallet items. Let’s overview how to integrate application with Wallet.

In this post I will overview application with following scenario: free application for “DevCorner Friends” club members, which provides club members with different coupons. In addition the club provides users with debit and credit cards. All those items will be stored in the Wallet.

Note: in sake of simplicity this application will fake server-side functionality.

 

Getting started

To use any Wallet related functionality the app should specify the following capabilities in the application manifest:

ID_CAP_WALLET – required to use all APIs from Microsoft.Phone.Wallet.

ID_CAP_WALLET_PAYMENTINSTRUMENTS – addition capability required to use PaymentInstrument and OnlinePaymentInstrument classes (credit and debit cards, online payments such as PayPal)

 

Membership card

As you remember from scenario the app is targeted for club members, so very first operation would be to check membership card in the Wallet:

private WalletTransactionItem LoadMembership()

{

    return Wallet.FindItem("membership") as WalletTransactionItem;

}

The following code snippet uses FindItem function from the Wallet object to check for specific item in the Wallet items connected/linked to the application.

Note: we will see more functions provided by Wallet object later in the post.

If card was not found application presents simple UI informing user, that this application could be used only with membership card:

image

Navigating to purchase page user is presented with standard in-app purchase screen suggesting to pay for membership (to get more info about in-app purchases functionality see my this post):

image

This app uses standard in-app purchase mechanism to buy membership and fulfills by saving new membership card into the Wallet:

private async void btnPurchase_Click(object sender, RoutedEventArgs e)

{

    var receipt = await Store.CurrentApp.RequestProductPurchaseAsync(productInfo.ProductId, true);

 

    if (productInfo.ProductType == Windows.ApplicationModel.Store.ProductType.Durable &&

        Store.CurrentApp.LicenseInformation.ProductLicenses[productInfo.ProductId].IsActive)

    {

        //Fulfill purchased durable

        NavigationService.Navigate(new Uri("/MembershipPage.xaml", UriKind.Relative));   

    }

 

    //...

}

MembershipPage is used to collect information need to create membership card – this includes fields required by the Wallet and custom fields required by our application and/or related server side functionality:

image

To save collected info we will be using new Task from Microsoft.Phone.Tasks namespace – the AddWalletItemTask:

AddWalletItemTask addWalletItemTask = new AddWalletItemTask();

addWalletItemTask.Completed += addWalletItemTask_Completed;

//...

 

private void addWalletItemTask_Completed(object sender, AddWalletItemResult e)

{

    if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.OK)

    {

        MessageBox.Show(e.Item.DisplayName + " was added to your wallet!");

 

        //...

    }

    else if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.Cancel)

    {

        MessageBox.Show("Cancelled");

 

        //...

    }

    else if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.None)

    {

        MessageBox.Show("None");

 

        //...

    }

}

 

private void btnSignUp_Click(object sender, RoutedEventArgs e)

{

    try

    {

        WalletTransactionItem membershipItem;

        membershipItem = new WalletTransactionItem("membership");

        membershipItem.IssuerName = "DevCorner";

        membershipItem.DisplayName = "DevCorner Friends Membership Card";

        membershipItem.IssuerPhone.Business = "+1 (425) 623 7371";

        membershipItem.CustomerName = FirstNameInput.Text + " " + LastNameInput.Text;

        membershipItem.AccountNumber = Guid.NewGuid().ToString();

        membershipItem.BillingPhone = PhoneNumberInput.Text;

        membershipItem.IssuerWebsite = new Uri("http://blogs.microsoft.co.il/blogs/alex_golesh");

        membershipItem.CustomProperties.Add("email", new CustomWalletProperty(EmailInput.Text));

        membershipItem.DisplayAvailableBalance = "100 credits";

        membershipItem.DisplayBalance = "100";

        BitmapImage bmp = new BitmapImage();

        using (System.IO.Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("WalletSample.Images.MembershipCard.png"))

            bmp.SetSource(stream);

 

        membershipItem.Logo99x99 = bmp;

        membershipItem.Logo159x159 = bmp;

        membershipItem.Logo336x336 = bmp;

        addWalletItemTask.Item = membershipItem;

        addWalletItemTask.Show();

    }

    catch (Exception ex)

    {

        MessageBox.Show("There were the following errors when saving your membership to the wallet: " + ex.Message);

    }

}

The following code snippet uses WalletTransactionItem object, fills the properties (predefined in application such as IssuerName, DisplayName, etc. and user inputs such as CustomerName, BillingPhone, etc.). In addition it uses CustomWalletProperty to extend standard properties with “email” property required by application/server side.

When WalletTransactionItem object is fully initialized the code snippet uses AddWalletItemTask to save new card to the the Wallet:

image

The result saved into the Wallet:

imageimage

Clicking on “open app” or message (right upper part, is available) takes user back to the application.

After membership card was purchased and in the Wallet the application should present valued club member with promised coupons/deals.

 

Deals

As you remember, when user starts the app it checks for membership card presence in the Wallet. If card does exists there the app is going to present the deals available for user:

membershipCard = LoadMembership();

 

if (null == membershipCard)

{

    //Membership is not found, suggest to purchase one

    //...    

}

else

{

    //Membership found

    await GetCoupons();

    //...

}

Note: In some case it makes sense to do additional checks such as membership card validity, expiration date, etc.

private async Task GetCoupons()

{

    List<ProductListing> products = new List<ProductListing>();

 

    var results = await Store.CurrentApp.LoadListingInformationByKeywordsAsync(new string[] { "coupon" });

 

    foreach (var product in results.ProductListings)

    {

        //Check is coupon already in the Wallet

        Deal coupon = Wallet.FindItem(product.Value.ProductId) as Deal;

        if (coupon == null)

            //Add product only if it is not in wallet anymore

            products.Add(product.Value);

    }

 

    lstCoupons.DataContext = products;

 

    //...

}

Note: This sample uses in-app purchase logic to present to user available deals and filters out the deals already present in the Wallet. This functionality is related to application’s business logic.

The resulting main screen:

image

Tapping on the coupon reveals same product screen as for membership card which enables purchasing the coupon:

image

The process of coupon fulfillment is similar to creating a new membership card:

if (productInfo.ProductType == Windows.ApplicationModel.Store.ProductType.Consumable &&

    Store.CurrentApp.LicenseInformation.ProductLicenses[productInfo.ProductId].IsActive)

{

    Deal deal = new Deal(productInfo.ProductId);

    deal.DisplayName = productInfo.Name;

    deal.Description = productInfo.Description;

    deal.IssuerName = "DevCorner";

    deal.IssuerWebsite = new Uri("http://blogs.microsoft.co.il/blogs/alex_golesh");

    deal.IsUsed = false;

    deal.MerchantName = "DevCorner";

    deal.TermsAndConditions = "This coupon expires after 7 calendar days from day of purchase.";

    deal.Code = Guid.NewGuid().ToString();

    deal.ExpirationDate = DateTime.Now.AddDays(7);

    deal.NavigationUri = new Uri("/CouponView.xaml?ID=" + productInfo.ProductId, UriKind.Relative);

    BitmapImage bmp = new BitmapImage(new Uri(string.Format("http://qr.kaywa.com/img.php?s=8&d={0}%25+off", productInfo.Tag), UriKind.Absolute));

    bmp.CreateOptions = BitmapCreateOptions.IgnoreImageCache;

    bmp.ImageOpened += async (s, args) =>

    {

        deal.BarcodeImage = bmp;

        await deal.SaveAsync();

    };

 

    //Report fulfillment

    Store.CurrentApp.ReportProductFulfillment(productInfo.ProductId);

 

    //...

}

Note: this sample uses free online QR generator (http://qrcode.kaywa.com/).

Resulting coupon in the Wallet:

imageimage

Last part – the application provides a page to show in-Wallet coupons. Loading the Wallet items:

private async void LoadCoupons()

{

    var myWalletItems = await Wallet.GetItemsAsync();

 

    var coupons = from walletItem in myWalletItems

                  where walletItem is Deal

                  select walletItem;

 

    lstCoupons.DataContext = coupons;

    

    //...

}

Note: this code snippet uses additional functionality GetItemsAsync to get all the associated items from the Wallet.

image

Selecting specific coupon shows the details:

image

This page enables user to use the coupon using AppBar button:

if (MessageBox.Show("Use the coupon?", "My Coupons", MessageBoxButton.OKCancel) == MessageBoxResult.OK)

{

    currentCoupon.IsUsed = true;

    await currentCoupon.SaveAsync();

 

    MessageBox.Show("Coupon used. Feel free to remove it form your wallet");

    NavigationService.GoBack();

}

Note: currentCoupon object is a Wallet Deal.

Used coupon reflected in the Wallet:

image

Once coupon is used it could be programmatically removed from the Wallet:

image

if (MessageBox.Show("Remove used coupon?", "My Coupons", MessageBoxButton.OKCancel) == MessageBoxResult.OK)

{

    Wallet.Remove(currentCoupon.Id);

    NavigationService.GoBack();

}

 

Payment Instruments

In addition to membership card and related deals, the application provides user with credit and debit cards.

image

Those cards implemented using PaymentInstrument class.

Note: using PaymentInstrument requires additional capabilities defined in application manifest (see above).

Working with PaymentInstrument is similar to working with other Wallet items:

var cards = from item in await Wallet.GetItemsAsync()

            where item is PaymentInstrument

            select item as PaymentInstrument;

Each PaymentInstrument has PaymentInstrumentKinds property which identifies its type:

public enum PaymentInstrumentKinds

{

    // No card is defined.

    None = 0,

    // Credit card.

    Credit = 1,

    //  Debit card.

    Debit = 2,

    //  Card used for credit and debit purchases.

    CreditAndDebit = 3,

    //  INICIS payment instrument. INICIS is an electronic payment gateway service company.

    Inicis = 64,

}

Note: in addition to PaymentInstrument, the Wallet supports OnlinePaymentInstrument with supported OnlinePaymentInstrumentKind:

public static class OnlinePaymentInstrumentKind

{

    // An AliPay™ payment.

    public const string AliPay = "AliPay";

    // A consumer-defined payment type.

    public const string ConsumerStoredValue = "ConsumerStoredValue";

    // A mobile operator payment.

    public const string MobileOperator = "MobileOperator";

    // A PayPal™ payment.

    public const string PayPal = "PayPal";

}

OnlinePaymentInstrument are not discussed in this post.

Saving PaymentInstrument to the Wallet is identical to other types of Wallet items – using AddWalletItemTask Task:

if (MessageBox.Show("This card is not in your wallet yet. Do you want to copy this card to the wallet?", 

                    "Add new card?", MessageBoxButton.OKCancel) == MessageBoxResult.OK)

{

    addWalletItemTask.Item = theCard;

    addWalletItemTask.Show();

}

Saved card in the Wallet:

imageimageimage

The card page in application shows same information as in the Wallet:

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

{

    string passedID = "";

    if (NavigationContext.QueryString.TryGetValue("ID", out passedID))

    {

        // Set the current deal.

        currentCard = Wallet.FindItem(passedID) as PaymentInstrument;

 

        // Set the data context to current deal.

        DataContext = currentCard;

 

        // Set the expiration text

        txtBalance.Text = "Balance: " + (currentCard.PaymentInstrumentKinds == PaymentInstrumentKinds.Debit ? currentCard.DisplayAvailableBalance : currentCard.DisplayAvailableCredit);

 

        lstTransactions.ItemsSource = currentCard.TransactionHistory.Values;

 

        if (currentCard.CustomProperties.ContainsKey("BillPosted"))

        {

            txtInfo.Visibility = Visibility.Visible;

            txtInfo.Text = "New bill is posted. Please pay it now";

 

            // Set the page's ApplicationBar to a new instance of ApplicationBar.

            ApplicationBar = new ApplicationBar();

 

            // Create a new button and set the text value to the localized string from AppResources.

            ApplicationBarIconButton appBarButton = new ApplicationBarIconButton(new Uri("/Images/PayBill.png", UriKind.Relative));

            appBarButton.Text = AppResources.appbar_buttonPayBillText;

            appBarButton.Click += appBarButton_Click;

            ApplicationBar.Buttons.Add(appBarButton);

        }

        else

        {

            ApplicationBar = null;

            txtInfo.Visibility = Visibility.Collapsed;

        }

    }

}

The code snippet above gets PaymentInstrument from the Wallet, binds transactions history ListBox to TansactionHistory object. Lastly, it checks for custom property “BillPosted” (will be added later in case of credit card) and suggest user to pay the bill:

imageimage

Paying the bill (in case of credit card) is a new page:

image

Paying bill involves your application’s business logic (probably contacting server side and performing transaction), but after it is done updating the card in the Wallet is simple:

if (currentCard.CustomProperties.ContainsKey("BillPosted"))

    currentCard.CustomProperties.Remove("BillPosted");

 

currentCard.Message = "";

currentCard.DisplayAvailableCredit = "1000";

currentCard.DisplayCreditLimit = 1000.ToString("C");

await currentCard.SaveAsync();

Note: CustomProperties is collection of properties associated with WalletItem which could be used to store application-specific info.

Through the post I presented the screenshots with transactions, messages and also credit card bill. In this last part I will show how to keep your wallet item updated.

 

Wallet Agent

The WalletAgent is special type of background agent which associated with the application and used by the Wallet to update wallet items. The agent is created as standard Scheduled Task Agent:

image

Once added, it should be referenced from the main application:

image

This is special type of agent which executed automatically and doesn’t requires any additional code from user (unlike standard scheduled agent). To implement it, ScehuledAgent must derive form WalletAgent class:

public class ScheduledAgent : WalletAgent

{

    //...

}

Also, this walled doesn’t supports Invoke overload, but requires to overload OnRefreshData function:

protected override void OnRefreshData(RefreshDataEventArgs args)

{

    //...

}

To add some message to user while processing the card se the following code snippet:

foreach (var item in args.Items)

{

    WalletTransactionItem membershipCard = item as WalletTransactionItem;

    if (null != membershipCard) //membership card

    {

        if (int.Parse(membershipCard.DisplayBalance) >= 50)

        {

            //Fake a deal for member...

            membershipCard.Message = "You might be interested in special deal for you. Click here to check it out!";

            membershipCard.MessageNavigationUri = new Uri("/MainPage.xaml", UriKind.Relative);

 

            //Fake usage

            membershipCard.DisplayBalance = (int.Parse(membershipCard.DisplayBalance) - 50).ToString();

            membershipCard.DisplayAvailableBalance = membershipCard.DisplayBalance + " credits";

 

            await membershipCard.SaveAsync();

        }

        else

        {

            membershipCard.Message = "You balanse is low. It is a time to fill it in. Click here to buy some credits!";

            membershipCard.MessageNavigationUri = new Uri("/Helpers/ProductsView.xaml?tag=credits", UriKind.Relative);

 

            await membershipCard.SaveAsync();

        }

    }

}

Note: this sample fakes transactions locally, while real app should get this information from application’s server side component.

Now let’s see how to add transactions and bills:

PaymentInstrument card = item as PaymentInstrument;

if (null != card)

{

    //Fake tranasaction -- pretend getting this data from server side component

    int transactionAmount = rnd.Next(-50, 0);

 

    WalletTransaction transaction = new WalletTransaction();

    transaction.Description = "Fake transaction";

    transaction.DisplayAmount = transactionAmount.ToString("C");

    transaction.TransactionDate = DateTime.Now;

 

    if (card.PaymentInstrumentKinds == PaymentInstrumentKinds.Credit) //DevCorner CreditCard

    {

        //Update balance

        int newBalance = int.Parse(card.DisplayAvailableCredit) + transactionAmount;

 

        card.DisplayAvailableCredit = newBalance.ToString();

        card.TransactionHistory.Add("FakeTransaction_" + transaction.TransactionDate, transaction);

 

        if (newBalance < 100)

        {

            //Signal new bill is available

            CustomWalletProperty billPostedProperty = new CustomWalletProperty("true");

            if (card.CustomProperties.ContainsKey("BillPosted"))

                card.CustomProperties.Remove("BillPosted");

 

            card.CustomProperties.Add("BillPosted", billPostedProperty);

 

            card.Message = "New bill posted. Click here to pay the bill.";

            card.MessageNavigationUri = new Uri("/PayBill.xaml?ID=" + card.Id, UriKind.Relative);

        }

    }

    else //Debit cards and other types of supported PaymentInstruments

    {

        //...

    }

 

    await card.SaveAsync();

}

The code snippet above creates new WalletTransaction object and populates it with info to be presented in the Wallet. Based on application’s logic, if card balance less than 100, the code adds new CustomWalletProperty which is used to signal user about new bill availability in the application. In addition, the code snippet uses same Message and MessageNavigationUri properties to show new bill info in the Wallet.

image

Note: Don’t forget to signal process completion by calling NotifyComplete();

Note: The simplest way to execute/debug WalletAgent is to select “refresh” command of your wallet item:

image

Now we have a complete application for scenario described at the beginning of the post.

 

Video of working app:

Sample wallet app

 

Enjoy and stay tuned,

Alex

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*