Overview of the Payment Instruments Sample App

,

The sample app for this section is a banking app that allows the user to retrieve a list of bank cards from a WCF service and to add the cards to the phone’s Wallet.


Note

The sample for this chapter requires access to a locally deployed WCF service. Please see Chapter 2, “Fundamental Concepts in Windows Phone Development,” for information on working with locally deployed WCF services.


The app has the following three views:

Image MainPage.xaml

Image CardView.xaml

Image BillPayView.xaml

MainPage.xaml displays the user’s cards and allows the user to tap on a card, which then navigates the app to the CardView.xaml page. The CardView.xaml page displays the card details and allows the user to add the card to, or remove the card from, the Wallet. In addition, the CardView.xaml page allows the user to reduce the card’s available funds by performing a pretend purchase (see Figure 25.2).

Image

FIGURE 25.2 The sample’s CouponView.xaml page.

The BillPayView.xaml page allows the user to replenish a card’s funds.

The sample app communicates with a WCF Service named BankService, which allows the user to retrieve a list of cards, make a credit card payment, get a list of transactions for a particular card, and to make purchases for credit and debit cards (see Listing 25.1).

The service uses two custom classes that represent a credit card and a debit card. The CreditCard and DebitCard classes both derive from the custom Card class.

The service uses the new awaitable asynchronous features of .NET. Each service method is decorated with an OperationContract attribute. The AsyncPattern property indicates that an operation is implemented asynchronously using either a Begin<methodName> and End<methodName> method pair in the service contract or, as demonstrated in the sample, the new async keyword combined with a Task return type.

LISTING 25.1. IBankService Interface


[ServiceContract]
[ServiceKnownType(typeof(CreditCard))]
[ServiceKnownType(typeof(DebitCard))]
public interface IBankService
{
    [OperationContract(AsyncPattern = true)]
    Task<IEnumerable<Card>> GetCards();

    [OperationContract(AsyncPattern = true)]
    Task<PerformPaymentResult> PerformPayment(CreditCard creditCard, decimal amount);

    [OperationContract(AsyncPattern = true)]
    Task<IEnumerable<AccountTransaction>> GetCardTransactions(Guid cardId);

    [OperationContract(AsyncPattern = true)]
    Task<MakePurchaseResult> MakeCreditPurchase(CreditCard creditCard, decimal amount);

    [OperationContract(AsyncPattern = true)]
    Task<MakePurchaseResult> MakeDebitPurchase(DebitCard debitCard, decimal amount);
}


The MainPageViewModel class retrieves the list of cards from the WCF service (see Listing 25.2).

The service reference code generation of the Windows Phone SDK does not, unfortunately, allow you to generate awaitable methods for asynchronous service methods (see Figure 25.3). Instead, you can use the Task class’s Factory object to create an awaitable task, which then allows the method to populate the viewmodel’s Card property without the use of an event handler.

Image

FIGURE 25.3 Windows Phone SDK does not allow for task-based operation generation.

The Factory class’s FromAsync method requires access to the BeginGetCards and EndGetCards methods of the IBankService. These interface members are explicitly implemented in the service client. We therefore cast the BankServiceClient object to the IBankService interface to access the methods.

LISTING 25.2. MainPageViewModel.Load Method (Payment Instrument App)


public async void Load()
{
    var client = new BankServiceClient();
    var bankService = (IBankService)client;

    try
    {
        IEnumerable<Card> retrievedCards
            = await Task<IEnumerable<Card>>.Factory.FromAsync(
                bankService.BeginGetCards, bankService.EndGetCards, null);

        Cards = new ObservableCollection<Card>(retrievedCards);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Unable to retrieve cards." + ex);
        MessageService.ShowError("Unable to retrieve cards.");
    }
}


The viewmodel retrieves the cards from the service and places them in an ObservableCollection, which is later materialized in the view.

On the server side, the WCF BankService sends back two dummy cards: a CreditCard and a DebitCard (see Listing 25.3). The async keyword means you no longer have to implement a BeginX and EndX method for the asynchronous service methods.

LISTING 25.3. BankService Class (excerpt)


public class BankService : IBankService
{
    readonly List<Card> cards = new List<Card>
        {
            new CreditCard
                {
                    Id = Guid.Parse("520E4521-A010-4155-9C61-0B31A0F59C8B"),
                    AccountNumber = "XXXX-XXXX-XXXX-1234",
                    CardName = "Unleashed Credit", CustomerName = "Alan Turing",
                    CreditLimit = 1000, AvailableCredit = 1000,
                    ExpirationDate = new DateTime(2014, 1, 1),
                    CultureName = "en-US"
                },
            new DebitCard
                {
                    Id = Guid.Parse("077C9304-9BD4-4527-8BB3-EED5F0A78284"),
                    AccountNumber = "XXXX-XXXX-XXXX-5432",
                    CardName = "Unleashed Debit", CustomerName = "Alan Turing",
                    Balance = 1000, ExpirationDate = new DateTime(2014, 1, 1),
                    CultureName = "en-GB"
                }
        };

    public async Task<IEnumerable<Card>> GetCards()
    {
        return cards;
    }
...
}


The custom Card class has several properties, including an AccountNumber property, a CardName property, a CustomerName property, and an ExpirationDate property.

MainPage.xaml displays the cards returned from the WCF service using a ListBox, as shown:

<ListBox ItemsSource="{Binding Cards}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel
                    v:Commanding.Command="{Binding Content.ViewCardCommand,
                    Source={StaticResource bridge}}"
                    v:Commanding.CommandParameter="{Binding}"
                    Margin="12,10,0,10">
                <TextBlock Text="{Binding CardName}" />
                <TextBlock Text="{Binding AccountNumber}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The custom commanding infrastructure handles the Tap event of the StackPanel, which causes the command’s Execute method to be called. The DataTemplate uses the bridge resource to resolve the viewmodel of the page, which is outside the scope of the DataTemplate. The bridge resource is a ContentControl, defined in the page resources as shown:

<phone:PhoneApplicationPage.Resources>
    <ContentControl x:Name="bridge"
                    Content="{Binding Path=DataContext, ElementName=page}" />
</phone:PhoneApplicationPage.Resources>

The MainPageViewModel class contains a ViewCardCommand property, so that when the user taps a card, the app navigates to the CardView.xaml page by using the viewmodel’s custom NavigationService, as shown:

void ViewCard(Card card)
{
    Navigate("/CardView/CardView.xaml?CardId=" + card.Id);
}

The CardView page receives the ID of the card via a querystring parameter, which it then provides to the viewmodel’s Load method (see Listing 25.4). The viewmodel parses the ID back to a Guid and, for the sake of simplicity, the Card is retrieved from the entire list of Card objects retrieved from the server.

The viewmodel’s Title property is set to the CardName property of the Card.

LISTING 25.4. CardViewModel.Load Method


public async void Load(IDictionary<string, string> queryDictionary)
{
    string cardIdValue;
    if (!queryDictionary.TryGetValue("CardId", out cardIdValue))
    {
        Debug.WriteLine("Dictionary does not contain CardId key.");
        return;
    }

    Guid cardId;
    if (!Guid.TryParse(cardIdValue, out cardId))
    {
        throw new Exception("CardId cannot be passed.");
    }

    var bankService = (IBankService)new BankServiceClient();

    try
    {
        IEnumerable<Card> cards = await Task<IEnumerable<Card>>.Factory.FromAsync(
            bankService.BeginGetCards, bankService.EndGetCards, null);

        Card = cards.SingleOrDefault(c => c.Id == cardId);
        if (card != null)
        {
            Title = card.CardName;
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Unable to retrieve cards." + ex);
        MessageService.ShowError("Unable to retrieve cards.");
    }

    Refresh();
}


The CardViewModel class contains a ToggleInWallet DelegateCommand that adds a PaymentInstrument representing the Card to the Wallet. If an item already exists for the card, the command removes the item from the Wallet.

Detecting the presence of a card in the Wallet is done using the Wallet class’s static FindItem method, as demonstrated in the following excerpt:

public bool InWallet
{
    get
    {
        return card != null && Wallet.FindItem(card.Id.ToString()) != null;
    }
}

The CardViewModel’s AddToWallet method constructs a PaymentInstrument using the card information (see Listing 25.5).

You can add custom properties to Wallet item objects. This allows you to add domain-specific information that is stored alongside a card.

In the sample, the culture name of the card is stored as a custom property, which allows the app to display currency values using the correct currency symbol and format. If the card is a CreditCard instance, its DisplayCreditLimit and DisplayAvailableCredit string properties are set using a CultureInfo object to produce the appropriate currency format.

The PaymentInstrument class’s Message and MessageNavigationUri allow you to display some text alongside the payment instrument’s logo in the Wallet. When the user taps the message in the Wallet, the Wallet navigates the user to a location in your app.

You must specify the DisplayName property of the Wallet item, along with logo images for each of its three logo properties. Your app may retrieve the image from the server or, as in the case of the sample, retrieve it locally from a Content resource in the app’s main assembly.

The NavigationUri property of the payment instrument is used to launch your app when the user taps the Open App link in the instrument’s About page in the Wallet.

LISTING 25.5. CardViewModel.AddToWallet Method


void AddToWallet()
{
    var instrument = new PaymentInstrument(card.Id.ToString())
        {
            AccountNumber = card.AccountNumber,
            CustomerName = card.CustomerName,
            ExpirationDate = card.ExpirationDate,
            DisplayName = card.CardName
        };

    /* The culture name is set as a custom wallet property. */
    var cultureCodeProperty = new CustomWalletProperty { Value = card.CultureName };
    instrument.CustomProperties.Add("CultureName", cultureCodeProperty);

    var cultureInfo = new CultureInfo(card.CultureName);

    var creditCard = card as CreditCard;
    DebitCard debitCard;

    if (creditCard != null)
    {
        instrument.PaymentInstrumentKinds = PaymentInstrumentKinds.Credit;

        instrument.DisplayCreditLimit
            = creditCard.CreditLimit.ToString("C", cultureInfo);
        instrument.DisplayAvailableCredit
            = creditCard.AvailableCredit.ToString("C", cultureInfo);

        /* Specify a message and a deep link URL that opens
            * the bill pay view from the Wallet. */
        instrument.Message = "Pay your credit card bill.";
        instrument.MessageNavigationUri = new Uri(
            "/BillPayView/BillPayView.xaml?CardId=" + card.Id, UriKind.Relative);
    }
    else if ((debitCard = card as DebitCard) != null)
    {
        instrument.PaymentInstrumentKinds = PaymentInstrumentKinds.Debit;

        instrument.DisplayBalance = debitCard.Balance.ToString("C", cultureInfo);
    }
    else
    {
        throw new Exception("Unknown card type: " + card.GetType());
    }

    /* Specify a logo that is displayed in the wallet. */
    var bitmapImage = new BitmapImage();
    var logoUri = new Uri("Assets/UnleashedBankIcon.png", UriKind.Relative);
    bitmapImage.SetSource(Application.GetResourceStream(logoUri).Stream);

    instrument.Logo99x99 = bitmapImage;
    instrument.Logo159x159 = bitmapImage;
    instrument.Logo336x336 = bitmapImage;

    /* Add a deep link so that when the app is launched from the Wallet item,
        * the card view page is shown instead of the main page. */
    instrument.NavigationUri = new Uri(
        "/CardView/CardView.xaml?CardId=" + card.Id, UriKind.Relative);

    addWalletItemTask.Item = instrument;
    addWalletItemTask.Show();
}


When the viewmodel calls the AddWalletItemTask object’s Show method, the user is presented with a built-in confirmation dialog (see Figure 25.4).

Image

FIGURE 25.4 Adding an item to the Wallet presents a built-in confirmation dialog.

You remove an item from the Wallet using the Wallet class’s static Remove method, as shown:

Wallet.Remove(card.Id.ToString());

Within the Wallet Hub, tapping a Wallet item displays the item’s details (see Figure 25.5). If the item’s TransactionHistory IDictionary has been populated, a pivot item listing all the transactions is included alongside the about pivot item.

Image

FIGURE 25.5 Viewing the Wallet item’s details in the Wallet Hub.

The value of the payment instrument’s Message property is displayed to the right of the logo. Tapping on the message navigates to the URI specified by the instrument’s MessageNavigationUri property. This capability to link back to your app allows you to notify the user of important information each time the user visits the Wallet Hub. In the next section you look at updating the message using a background agent.

The server-side BankService includes a MakeCreditPurchase method that deducts an amount from a specified credit card by reducing its AvailableCredit property (see Listing 25.6). The service method returns a Success result if the credit card is located and its available credit reduced.

LISTING 25.6. BankService.MakeCreditPurchase Method


public async Task<MakePurchaseResult> MakeCreditPurchase(
    CreditCard creditCard, decimal amount)
{
    CreditCard existingCard = (CreditCard)cards.Single(
                                    card => card.Id == creditCard.Id);

    if (existingCard == null)
    {
        return MakePurchaseResult.NoSuchCard;
    }

    decimal newAvailableCredit = existingCard.AvailableCredit - amount;
    if (newAvailableCredit < 0)
    {
        return MakePurchaseResult.AmountExceedsAvailableFunds;
    }

    AccountTransaction transaction = new AccountTransaction(
                                        "Purchase", "+" + amount, DateTime.Now);
    List<AccountTransaction> transactionList = GetTransactionList(creditCard.Id);
    transactionList.Add(transaction);

    existingCard.AvailableCredit = existingCard.AvailableCredit - amount;

    return MakePurchaseResult.Success;
}


The BankingService class also includes a MakeDebitPurchase method that behaves the same way as the MakeCreditPurchase method, but instead of reducing the available credit, it reduces the DebitCard object’s Balance.

The BankService class maintains transaction history for each card by storing custom AccountTransaction objects in a Dictionary. When a caller requests the list of transactions for a particular card, and it is the first request, the service creates a new List<AccountTransaction> object as shown:

readonly Dictionary<Guid, List<AccountTransaction>> accountTransactions
                        = new Dictionary<Guid, List<AccountTransaction>>();

List<AccountTransaction> GetTransactionList(Guid cardId)
{
    List<AccountTransaction> result;

    if (!accountTransactions.TryGetValue(cardId, out result))
    {
        result = new List<AccountTransaction>();
        accountTransactions.Add(cardId, result);
    }

    return result;
}

The CardViewModel class calls either the WCF service’s MakeCreditPurchase or MakeDebitPurchase method, depending on the card type, when a MakePurchaseCommand is executed (see Listing 25.7).

We create a CultureInfo object using the CultureName property of the card. A string representing the monetary amount of the purchase is constructed using the CultureInfo object, which ensures that the correct currency format and symbol are materialized.

LISTING 25.7. CardViewModel.MakePurchaseCore Method


async Task MakePurchaseCore()
{
    var bankService = (IBankService)new BankServiceClient();
    MakePurchaseResult purchaseResult;

    CreditCard creditCard = card as CreditCard;
    if (creditCard != null)
    {
        purchaseResult = await Task<MakePurchaseResult>.Factory.FromAsync(
                                    bankService.BeginMakeCreditPurchase,
                                    bankService.EndMakeCreditPurchase,
                                    creditCard, purchaseAmount, null);
    }
    else
    {
        purchaseResult = await Task<MakePurchaseResult>.Factory.FromAsync(
                                    bankService.BeginMakeDebitPurchase,
                                    bankService.EndMakeDebitPurchase,
                                    (DebitCard)card, purchaseAmount, null);
    }

    string amountString = purchaseAmount.ToString(
                            "C", new CultureInfo(card.CultureName));

    if (purchaseResult == MakePurchaseResult.Success)
    {
        MessageService.ShowMessage("Purchase made for " + amountString);
    }
    else if (purchaseResult == MakePurchaseResult.AmountExceedsAvailableFunds)
    {
        MessageService.ShowMessage("Purchase amount exceeds available funds.");
    }
    else
    {
        MessageService.ShowError("Unable to perform purchase.");
    }
}


Relying on your foreground app to update a payment instrument can lead to out-of-date information in the Wallet. Fortunately, the Windows Phone SDK has tight integration with a specific type of background agent: the WalletAgent, which allows you to receive notification when the OS senses that a Wallet item may require updating or when a user taps the Refresh application bar menu item in the Wallet.

In the next section, you look at using a custom WalletAgent implementation to update a Wallet item’s information from a cloud service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.56.28